diff --git a/arkoala/ets-plugin/src/ArkExpander.ts b/arkoala/ets-plugin/src/ArkExpander.ts index 500f863177816828069c28274ed2202b81d5ce65..4a89f1217a95cb49622e956debee4d5dde4bf323 100644 --- a/arkoala/ets-plugin/src/ArkExpander.ts +++ b/arkoala/ets-plugin/src/ArkExpander.ts @@ -131,7 +131,7 @@ export function arkExpandFile( const extensionStylesTransformer = new ExtensionStylesTransformer(sourceFile, ctx, typeChecker, importer) const preprocessorTransformer = new EtsFirstArgTransformer(sourceFile, ctx, importer, callTable) const styleTransformer = new StyleTransformer(sourceFile, ctx, typeChecker, importer, extensionStylesTransformer, callTable) - const structTransformer = new StructTransformer(sourceFile, ctx, typeChecker, importer, nameTable, entryTracker, callTable) + const structTransformer = new StructTransformer(sourceFile, ctx, typeChecker, importer, nameTable, entryTracker, callTable, extras) const nameCollector = new NameCollector(sourceFile, ctx, nameTable, issueTable) const dollarTransformer = new DollarTransformer(sourceFile, ctx, nameTable) const abilityTransformer = new AbilityTransformer(sourceFile, ctx, importer) diff --git a/arkoala/ets-plugin/src/PropertyTranslators.ts b/arkoala/ets-plugin/src/PropertyTranslators.ts index 3f328e7a8dd31c13f6cfb6c5fead04a7e9630f5e..7b956e0bc46d557d3e865dfe4fdef1f90bdec480 100644 --- a/arkoala/ets-plugin/src/PropertyTranslators.ts +++ b/arkoala/ets-plugin/src/PropertyTranslators.ts @@ -70,13 +70,25 @@ import { StorageLinkDecorator, StoragePropDecorator, SyncedPropertyConstructor, - throwError, WatchDecorator, } from "./utils" +import { MessageCode, reportError } from "./diagnostics" + +export class PropertyTranslatorContext { + constructor( + public importer: Importer, + public sourceFile: ts.SourceFile, + public extras?: ts.TransformerExtras, + ) {} +} {} + export abstract class PropertyTranslator { private cachedType: ts.TypeNode | undefined // do not analyze this.property.initializer every time - constructor(protected property: ts.PropertyDeclaration, protected importer: Importer) { } + constructor( + protected property: ts.PropertyDeclaration, + protected context: PropertyTranslatorContext + ) { } get propertyName(): string { return idTextOrError(this.property.name) @@ -101,7 +113,21 @@ export abstract class PropertyTranslator { } createStateOf(type: ts.TypeNode | undefined, ...initializer: ts.Expression[]): ts.Expression { - return createStateOf(this.importer, type, ...initializer) + return createStateOf(this.context.importer, type, ...initializer) + } + + typeOrErrorType(node: ts.Node, type: ts.TypeNode|undefined): ts.TypeNode { + if (!type) { + reportError( + MessageCode.EXPECTED_STRUCT_FIELD__TO_BE_EXPLICITLY_TYPED, + `Expected struct field to be explicitly typed in arkts 2.0 app: ${node.getText()}`, + node, + this.context.sourceFile, + this.context.extras + ) + } + return type ?? + ts.factory.createTypeReferenceNode("") } translateStateMember( @@ -161,7 +187,7 @@ export abstract class PropertyTranslator { mutableState(type: ts.TypeNode,): ts.TypeNode { return ts.factory.createTypeReferenceNode( - id(this.importer.withRuntimeImport("MutableState")), + id(this.context.importer.withRuntimeImport("MutableState")), [type] ) } @@ -177,8 +203,8 @@ export abstract class PropertyTranslator { undefined, ts.factory.createTypeReferenceNode( syncedProperty - ? this.importer.withAdaptorImport("SyncedProperty") - : this.importer.withRuntimeImport("MutableState"), + ? this.context.importer.withAdaptorImport("SyncedProperty") + : this.context.importer.withRuntimeImport("MutableState"), [ this.propertyType ] @@ -197,7 +223,7 @@ export abstract class PropertyTranslator { protected createAppStorageState(decoratorName: string): ts.Expression { return ts.factory.createCallExpression( - id(this.importer.withAdaptorImport("AppStorageLinkState")), + id(this.context.importer.withAdaptorImport("AppStorageLinkState")), [this.propertyType], [ findDecoratorArgument(filterDecorators(this.property), decoratorName, 0), @@ -208,7 +234,7 @@ export abstract class PropertyTranslator { protected createLocalStorageState(decoratorName: string): ts.Expression { return ts.factory.createCallExpression( - id(this.importer.withAdaptorImport("StorageLinkState")), + id(this.context.importer.withAdaptorImport("StorageLinkState")), [this.propertyType], [ createThisFieldAccess(LocalStoragePropertyName), @@ -281,7 +307,7 @@ export abstract class PropertyTranslator { ) ), ts.factory.createCallExpression( - id(this.importer.withCommonImport("observableProxy")), + id(this.context.importer.withCommonImport("observableProxy")), undefined, [ id("value"), @@ -339,7 +365,7 @@ export abstract class PropertyTranslator { translateInitializerOfSyncedProperty(constructorName: SyncedPropertyConstructor, withValue?: ts.Expression): ts.Expression { return ts.factory.createCallExpression( - id(this.importer.withAdaptorImport(constructorName)), + id(this.context.importer.withAdaptorImport(constructorName)), this.property.type ? [this.property.type] : undefined, withValue ? [withValue] : [] ) @@ -400,14 +426,21 @@ export abstract class PropertyTranslator { protected stateImplField(impl: StateImplementation): { name: ts.Identifier; type: ts.TypeNode }[] { const originalName = asIdentifier(this.property.name) - const originalType = this.property.type ?? throwError( - `Expected struct field to be explicitly typed in arkts 2.0 app: ${this.property.getText()}` - ) + if (!this.property.type) { + reportError( + MessageCode.EXPECTED_STRUCT_FIELD__TO_BE_EXPLICITLY_TYPED, + `Expected struct field to be explicitly typed in arkts 2.0 app: ${this.property.getText()}`, + this.property, + this.context.sourceFile, + this.context.extras + ) + } + const originalType = this.typeOrErrorType(this.property, this.property.type) const backingName = backingFieldName(asIdentifier(this.property.name)) const backingType = ts.factory.createTypeReferenceNode( (impl === StateImplementation.SyncedProperty) ? impl : - this.importer.withRuntimeImport(impl), + this.context.importer.withRuntimeImport(impl), [originalType] ) @@ -419,9 +452,7 @@ export abstract class PropertyTranslator { protected plainImplField(): { name: ts.Identifier; type: ts.TypeNode }[] { const name = asIdentifier(this.property.name) - const type = this.property.type ?? throwError( - `Expected struct field to be explicitly typed in arkts 2.0 app: ${this.property.getText()}` - ) + const type = this.typeOrErrorType(this.property, this.property.type) return [{ name, type }] } @@ -505,7 +536,7 @@ class Prop extends PropertyTranslator { return this.translateToUpdateSyncedProperty(createNullableAccessor(initializers(), this.propertyName)) } translateToBuildParameter(): ts.ParameterDeclaration { - return translatePropOrObjectLinkToBuildParameter(this.property, this.importer) + return translatePropOrObjectLinkToBuildParameter(this.property, this.context.importer) } } @@ -547,7 +578,7 @@ class ObjectLink extends PropertyTranslator { return this.translateToUpdateSyncedProperty(createNullableAccessor(initializers(), this.propertyName)) } translateToBuildParameter(): ts.ParameterDeclaration { - return translatePropOrObjectLinkToBuildParameter(this.property, this.importer) + return translatePropOrObjectLinkToBuildParameter(this.property, this.context.importer) } } @@ -595,12 +626,12 @@ class StorageLink extends PropertyTranslator { public implField(): { name: ts.Identifier; type: ts.TypeNode }[] { const name = backingFieldName(asIdentifier(this.property.name)) const type = ts.factory.createTypeReferenceNode( - this.importer.withRuntimeImport("MutableState"), - [this.property.type ?? throwError(`Expected struct field to be explicitly typed in arkts 2.0 app: ${this.property.getText()}`)] + this.context.importer.withRuntimeImport("MutableState"), + [this.typeOrErrorType(this.property, this.property.type)] ) const name1 = asIdentifier(this.property.name) - const type1 = this.property.type ?? throwError(`Expected struct field to be explicitly typed in arkts 2.0 app: ${this.property.getText()}`) + const type1 = this.typeOrErrorType(this.property, this.property.type) return [{ name, type }, { name: name1, type: type1 }] } @@ -694,8 +725,8 @@ export class BuilderParam extends PropertyTranslator { return this.initializePlain(initializers) } - constructor(protected property: ts.PropertyDeclaration, protected importer: Importer, private typechecker: ts.TypeChecker) { - super(property, importer) + constructor(protected property: ts.PropertyDeclaration, protected context: PropertyTranslatorContext) { + super(property, context) } translateMember(): ts.ClassElement[] { return this.translatePlainMember( @@ -720,8 +751,8 @@ class PlainProperty extends PropertyTranslator { return this.initializePlain(initializers) } - constructor(protected property: ts.PropertyDeclaration, protected importer: Importer, private typechecker: ts.TypeChecker) { - super(property, importer) + constructor(protected property: ts.PropertyDeclaration, protected context: PropertyTranslatorContext) { + super(property, context) } translateMember(): ts.ClassElement[] { return this.translatePlainMember( @@ -763,20 +794,20 @@ function translatePropOrObjectLinkToBuildParameter(property: ts.PropertyDeclarat ) } -export function classifyProperty(member: ts.ClassElement, importer: Importer, typechecker: ts.TypeChecker): PropertyTranslator | undefined { +export function classifyProperty(member: ts.ClassElement, context: PropertyTranslatorContext): PropertyTranslator | undefined { if (!ts.isPropertyDeclaration(member)) return undefined - if (isState(member)) return new State(member, importer) - if (isStorageProp(member)) return new StorageProp(member, importer) - if (isStorageLink(member)) return new StorageLink(member, importer) - if (isLocalStorageLink(member)) return new LocalStorageLink(member, importer) - if (isLocalStorageProp(member)) return new LocalStorageProp(member, importer) - if (isLink(member)) return new Link(member, importer) - if (isProp(member)) return new Prop(member, importer) - if (isObjectLink(member)) return new ObjectLink(member, importer) - if (isProvide(member)) return new Provide(member, importer) - if (isConsume(member)) return new Consume(member, importer) - if (isBuilderParam(member)) return new BuilderParam(member, importer, typechecker) - - return new PlainProperty(member, importer, typechecker) + if (isState(member)) return new State(member, context) + if (isStorageProp(member)) return new StorageProp(member, context) + if (isStorageLink(member)) return new StorageLink(member, context) + if (isLocalStorageLink(member)) return new LocalStorageLink(member, context) + if (isLocalStorageProp(member)) return new LocalStorageProp(member, context) + if (isLink(member)) return new Link(member, context) + if (isProp(member)) return new Prop(member, context) + if (isObjectLink(member)) return new ObjectLink(member, context) + if (isProvide(member)) return new Provide(member, context) + if (isConsume(member)) return new Consume(member, context) + if (isBuilderParam(member)) return new BuilderParam(member, context) + + return new PlainProperty(member, context) } diff --git a/arkoala/ets-plugin/src/StructOptions.ts b/arkoala/ets-plugin/src/StructOptions.ts index ec68a5fe2f8637b2574ea76c5847b149a907b0a0..957b9e8684ffabc399016da2d29cd54eba82629a 100644 --- a/arkoala/ets-plugin/src/StructOptions.ts +++ b/arkoala/ets-plugin/src/StructOptions.ts @@ -1,22 +1,22 @@ import * as ts from "@koalaui/ets-tsc" import { sourceStructName } from "./ApiUtils" import { adaptorClassName, isDefined, throwError } from "./utils" -import { classifyProperty } from "./PropertyTranslators" +import { PropertyTranslatorContext, classifyProperty } from "./PropertyTranslators" import { Importer } from "./Importer" import { ImportExport } from "./import-export"; export class StructOptions { private importExport: ImportExport constructor( - private typechecker: ts.TypeChecker, - private importer: Importer + private context: PropertyTranslatorContext, + private typechecker: ts.TypeChecker ) { this.importExport = new ImportExport(this.typechecker) } private implProperties(node: ts.StructDeclaration): { name: ts.Identifier, type: ts.TypeNode }[] { return node.members - .map(it => classifyProperty(it, this.importer, this.typechecker)) + .map(it => classifyProperty(it, this.context)) .filter(isDefined) .flatMap(it => it.implField()) } @@ -53,7 +53,7 @@ export class StructOptions { public updatedInitializersValue(node: ts.StructDeclaration, instance: ts.Identifier) { return ts.factory.createObjectLiteralExpression( node.members - .map(it => classifyProperty(it, this.importer, this.typechecker)) + .map(it => classifyProperty(it, this.context)) .filter(isDefined) .flatMap(it => it.toInitialization(instance)), true diff --git a/arkoala/ets-plugin/src/StructTransformer.ts b/arkoala/ets-plugin/src/StructTransformer.ts index 765bca56b3787e74b66cb4ad39284086bec0397f..9cedcda754f5f1d3b32b559c4a77d10e3bdda790 100644 --- a/arkoala/ets-plugin/src/StructTransformer.ts +++ b/arkoala/ets-plugin/src/StructTransformer.ts @@ -62,7 +62,7 @@ import { voidLambdaType, WatchDecorator } from './utils' -import { BuilderParam, classifyProperty, ClassState, PropertyTranslator } from './PropertyTranslators' +import { BuilderParam, classifyProperty, ClassState, PropertyTranslator, PropertyTranslatorContext } from './PropertyTranslators' import { asIdentifier, assignment, @@ -89,7 +89,7 @@ import { StructOptions } from "./StructOptions" export class StructTransformer extends AbstractVisitor { private structOptions: StructOptions - + private propertyTranslatorContext: PropertyTranslatorContext constructor( sourceFile: ts.SourceFile, ctx: ts.TransformationContext, @@ -97,12 +97,14 @@ export class StructTransformer extends AbstractVisitor { public importer: Importer, public nameTable: NameTable, public entryTracker: EntryTracker, - public callTable: CallTable + public callTable: CallTable, + public extras?: ts.TransformerExtras ) { super(sourceFile, ctx) this.importer.addAdaptorImport(adaptorComponentName("PageTransitionEnter")) this.importer.addAdaptorImport(adaptorComponentName("PageTransitionExit")) - this.structOptions = new StructOptions(this.typechecker, this.importer) + this.propertyTranslatorContext = new PropertyTranslatorContext(importer, sourceFile, extras) + this.structOptions = new StructOptions(this.propertyTranslatorContext, this.typechecker) } dropImportEtsExtension(node: ts.ImportDeclaration, oldLiteral: string): ts.ImportDeclaration { @@ -840,7 +842,7 @@ export class StructTransformer extends AbstractVisitor { ) const propertyTranslators = filterDefined( - node.members.map(it => classifyProperty(it, this.importer, this.typechecker)) + node.members.map(it => classifyProperty(it, this.propertyTranslatorContext)) ) const translatedMembers = this.translateStructMembers(node, propertyTranslators) @@ -1130,7 +1132,7 @@ export class StructTransformer extends AbstractVisitor { if (!ts.isPropertyDeclaration(it) || !isState(it)) { return it } - return new ClassState(it, this.importer).translateMember() + return new ClassState(it, this.propertyTranslatorContext).translateMember() }) const createdClass = ts.factory.updateClassDeclaration( diff --git a/arkoala/ets-plugin/src/diagnostics.ts b/arkoala/ets-plugin/src/diagnostics.ts new file mode 100644 index 0000000000000000000000000000000000000000..a580fd36396c37175a4a4c12c553da8cb86f3a79 --- /dev/null +++ b/arkoala/ets-plugin/src/diagnostics.ts @@ -0,0 +1,18 @@ +import * as ts from "@koalaui/ets-tsc" + +export enum MessageCode { + EXPECTED_STRUCT_FIELD__TO_BE_EXPLICITLY_TYPED = 20001 + +} + +export function reportError(code: number, text: string, node: ts.Node, sourceFile: ts.SourceFile, extras?: ts.TransformerExtras) { + const lineAndChar = ts.getLineAndCharacterOfPosition(sourceFile, node.pos) + extras?.addDiagnostic({ + code: code, + messageText: text, + category: ts.DiagnosticCategory.Error, + file: sourceFile, + start: node.pos, + length: node.end - node.pos + }) ?? console.log(text) +}