import { default as ts } from 'typescript'; import { ParsedPropertyDecorator } from './property.ts'; import { ApiCustomElementField } from '@arcgis/api-extractor/apiJson'; /** * In Stencil, the compiler uses type checking to determine if the inferred * property type is number or boolean, and if so, would make sure to cast the * string attribute value to number or boolean. Even in development mode type * checking is used for that - which is not ideal as it slows down the dev server. * * That is why in Lumina we are no doing type checking at all (not even * creating a TypeScript program) in the dev server. That of course puts some * constraints on us. * * In Lit, since there is no compiler, Lit relies on the `{type: Number}` or * `{type: Boolean}` property being passed to the `@property` decorator to * determine if the property should be cast. * * Looking at all usages of number and boolean properties in Calcite and * map-components, I see a few patterns: * * - In Calcite: * * - all boolean properties either explicitly have "false" as a default value, * or explicitly have `boolean` type in TypeScript - in this case the AST * contains enough information for lumina-compiler to insert `{type: Boolean}` * in the code at build-time. * - most number properties either have `number` type in TypeScript or numeric * default value. This however is not always the case as there are a few * exceptions: * * - calcite-action-group.columns: uses `Columns` number enum as it's type * - calcite-block.headingLevel: uses `HeadingLevel` number enum as it's * type * - calcite-pagination.maxItems, calcite-popover.offsetDistance and * calcite-tooltip.offsetDistance: use a const variable of numeric type as * their default value * * These cases can't be inferred from the AST alone * * - In Map Components: * * - here the story is complicated by the fact that we use the following * Controllers syntax in a lot of places: * * ```tsx * \@Prop() hideCreateFeaturesSection = this.widget.visibleElements.createFeaturesSection; * ``` * * In the above, the type is boolean, but we can't know that from the AST. * * Since we can use the type checker during build, I am considering solving the * issue like this: * * - both in serve and build mode, look at the AST to see if we can trivially * infer whether the property type is number or boolean * - in build mode: * - if has a custom converter specified, don't do anything * - else, if trivially inferred that property is boolean and number: * - if has explicit type annotation: * - if type annotation is of type number or boolean: * - edit the source code to automatically remove the useless type * annotation, and add a small log message to notify dev about what * happened * - warn about mismatching type annotation * - add {type:Number} or {type:Boolean} to the .js output code, not source * code * - else, use the type checker, with an algorithm similar to Stencil to * detect if property type is number or boolean * - if detected that property type is not number or boolean * - don't do anything * - else, if property type is number or boolean * - edit the source file to automatically add the type annotation * and add a small log message to notify dev about what happened * - add `{type:Number}` or `{type:Boolean}` to the .js output code * - else, don't do anything * - in dev mode: * - if type or converter is explicitly present * - don't do anything * - if inferred that property type is number or boolean * - add `{type:Number}` or `{type:Boolean}` to the .js output code, not * source code * * The benefits of the above: * * - dev build is fast as no type checking is happening * - source code is clean as type annotations are not present, unless ambiguous * - in ambiguous cases, the source code has an explicit annotation, which * ensures the serve mode has the same behavior as build for all properties * - ambiguous newly added properties in serve mode won't yet have an explicit * type annotation, but that is okay as ambiguous number/boolean properties * are not that common, and in development mode properties are often set as * JavaScript properties rather than HTML attributes, thus bypassing the * need to cast the property * - when converting a codebase from Stencil to Lit (either manually or using * the codemod), the type annotations in ambiguous places would be inserted * automatically if not yet present * - when converting wrapped map-components widget to native, most usages of a * code style like this will be removed, to be replaced with unambiguous * `false` default value: * * ```tsx * \@Prop() hideCreateFeaturesSection = this.widget.visibleElements.createFeaturesSection; * ``` * * In such cases, the above algorithm will automatically remove the now * useless type annotation. * * See also https://github.com/runem/lit-analyzer/blob/master/packages/lit-analyzer/src/lib/rules/no-incompatible-property-type.ts */ export declare function transformPropertyOptions(decorator: ParsedPropertyDecorator, sourceFile: ts.SourceFile, apiProperty: ApiCustomElementField): ts.ObjectLiteralExpression | undefined;