diff --git a/.eslintrc.cjs b/.eslintrc.cjs index 3ba62e3992..6ecf0a15f4 100644 --- a/.eslintrc.cjs +++ b/.eslintrc.cjs @@ -2,6 +2,12 @@ module.exports = { root: true, + ignorePatterns: [ + // These fixtures intentionally exercise AssemblyScript-only syntax that + // TypeScript's parser rejects before lint rules can run. + "tests/compiler/parameter-decorators.ts", + "tests/transform/parameter-decorators.ts" + ], parser: "@typescript-eslint/parser", plugins: [ "@typescript-eslint", diff --git a/cli/index.d.ts b/cli/index.d.ts index 58c7fb4ef6..3238d925b9 100644 --- a/cli/index.d.ts +++ b/cli/index.d.ts @@ -276,7 +276,10 @@ export abstract class Transform { /** Called when parsing is complete, before a program is instantiated from the AST. */ afterParse?(parser: Parser): void | Promise; - /** Called after the program is instantiated. */ + /** + * Called after the program is instantiated and before compilation-time validation runs. + * This is the last hook where transforms can rewrite preserved AST-only syntax before it is rejected. + */ afterInitialize?(program: Program): void | Promise; /** Called when compilation is complete, before the module is being validated. */ diff --git a/cli/index.js b/cli/index.js index d6808957f4..4d8939cb4d 100644 --- a/cli/index.js +++ b/cli/index.js @@ -718,7 +718,7 @@ export async function main(argv, options) { stats.initializeTime += stats.end(begin); } - // Call afterInitialize transform hook + // Call afterInitialize transform hook, the last AST rewrite point before compilation-time validation. { let error = await applyTransform("afterInitialize", program); if (error) return prepareResult(error); diff --git a/package.json b/package.json index 627dc00210..da10621201 100644 --- a/package.json +++ b/package.json @@ -84,8 +84,8 @@ "test:browser": "node --enable-source-maps tests/browser", "test:asconfig": "cd tests/asconfig && npm run test", "test:transform": "npm run test:transform:esm && npm run test:transform:cjs", - "test:transform:esm": "node bin/asc tests/compiler/empty --transform ./tests/transform/index.js --noEmit && node bin/asc tests/compiler/empty --transform ./tests/transform/simple.js --noEmit", - "test:transform:cjs": "node bin/asc tests/compiler/empty --transform ./tests/transform/cjs/index.js --noEmit && node bin/asc tests/compiler/empty --transform ./tests/transform/cjs/simple.js --noEmit", + "test:transform:esm": "node bin/asc tests/compiler/empty --transform ./tests/transform/index.js --noEmit && node bin/asc tests/compiler/empty --transform ./tests/transform/simple.js --noEmit && node bin/asc tests/transform/parameter-decorators.ts --transform ./tests/transform/remove-parameter-decorators.js --noEmit", + "test:transform:cjs": "node bin/asc tests/compiler/empty --transform ./tests/transform/cjs/index.js --noEmit && node bin/asc tests/compiler/empty --transform ./tests/transform/cjs/simple.js --noEmit && node bin/asc tests/transform/parameter-decorators.ts --transform ./tests/transform/cjs/remove-parameter-decorators.js --noEmit", "test:cli": "node tests/cli/options.js", "asbuild": "npm run asbuild:debug && npm run asbuild:release", "asbuild:debug": "node bin/asc --config src/asconfig.json --target debug", diff --git a/src/ast.ts b/src/ast.ts index 01d8e9a421..ba158f2f6b 100644 --- a/src/ast.ts +++ b/src/ast.ts @@ -181,9 +181,12 @@ export abstract class Node { name: IdentifierExpression, type: TypeNode, initializer: Expression | null, - range: Range + range: Range, + decorators: DecoratorNode[] | null = null ): ParameterNode { - return new ParameterNode(parameterKind, name, type, initializer, range); + let parameter = new ParameterNode(parameterKind, name, type, initializer, range); + parameter.decorators = decorators; + return parameter; } // special @@ -926,6 +929,9 @@ export class FunctionTypeNode extends TypeNode { ) { super(NodeKind.FunctionType, isNullable, range); } + + /** Decorators on an explicit `this` parameter, if any, preserved for transforms. */ + explicitThisDecorators: DecoratorNode[] | null = null; } /** Represents a type parameter. */ @@ -971,6 +977,8 @@ export class ParameterNode extends Node { super(NodeKind.Parameter, range); } + /** Decorators, if any, preserved so transforms can rewrite them before validation. */ + decorators: DecoratorNode[] | null = null; /** Implicit field declaration, if applicable. */ implicitFieldDeclaration: FieldDeclaration | null = null; /** Common flags indicating specific traits. */ diff --git a/src/compiler.ts b/src/compiler.ts index 36f09ab8aa..12d04785c0 100644 --- a/src/compiler.ts +++ b/src/compiler.ts @@ -536,6 +536,8 @@ export class Compiler extends DiagnosticEmitter { // initialize lookup maps, built-ins, imports, exports, etc. this.program.initialize(); + // Reject any parameter decorators that transforms left on the AST. + this.program.validateParameterDecorators(); // Binaryen treats all function references as being leaked to the outside world when diff --git a/src/extra/ast.ts b/src/extra/ast.ts index ebe9217f90..e6bcb6c982 100644 --- a/src/extra/ast.ts +++ b/src/extra/ast.ts @@ -427,6 +427,7 @@ export class ASTBuilder { sb.push(isNullable ? "((" : "("); let explicitThisType = node.explicitThisType; if (explicitThisType) { + this.serializeParameterDecorators(node.explicitThisDecorators); sb.push("this: "); this.visitTypeNode(explicitThisType); } @@ -1153,6 +1154,7 @@ export class ASTBuilder { let numParameters = parameters.length; let explicitThisType = signature.explicitThisType; if (explicitThisType) { + this.serializeParameterDecorators(signature.explicitThisDecorators); sb.push("this: "); this.visitTypeNode(explicitThisType); } @@ -1563,9 +1565,38 @@ export class ASTBuilder { indent(sb, this.indentLevel); } + serializeParameterDecorators(decorators: DecoratorNode[] | null): void { + if (decorators) { + for (let i = 0, k = decorators.length; i < k; ++i) { + this.serializeParameterDecorator(decorators[i]); + } + } + } + + private serializeParameterDecorator(node: DecoratorNode): void { + let sb = this.sb; + sb.push("@"); + this.visitNode(node.name); + let args = node.args; + if (args) { + sb.push("("); + let numArgs = args.length; + if (numArgs) { + this.visitNode(args[0]); + for (let i = 1; i < numArgs; ++i) { + sb.push(", "); + this.visitNode(args[i]); + } + } + sb.push(")"); + } + sb.push(" "); + } + serializeParameter(node: ParameterNode): void { let sb = this.sb; let kind = node.parameterKind; + this.serializeParameterDecorators(node.decorators); let implicitFieldDeclaration = node.implicitFieldDeclaration; if (implicitFieldDeclaration) { this.serializeAccessModifiers(implicitFieldDeclaration); diff --git a/src/index-wasm.ts b/src/index-wasm.ts index ec51de73da..15a5ff4347 100644 --- a/src/index-wasm.ts +++ b/src/index-wasm.ts @@ -347,7 +347,7 @@ export function getDependee(program: Program, file: string): string | null { // Compiler -/** Initializes the program pre-emptively for transform hooks. */ +/** Initializes the program pre-emptively so `afterInitialize` transforms can rewrite the AST before compilation. */ export function initializeProgram(program: Program): void { program.initialize(); } diff --git a/src/parser.ts b/src/parser.ts index 7c69843973..a5a14adec7 100644 --- a/src/parser.ts +++ b/src/parser.ts @@ -698,17 +698,19 @@ export class Parser extends DiagnosticEmitter { // Indicates whether tryParseSignature determined that it is handling a Signature private tryParseSignatureIsSignature: bool = false; - /** Parses a function type, as used in type declarations. */ + /** Parses a function type, preserving parameter decorators for transforms. */ tryParseFunctionType( tn: Tokenizer ): FunctionTypeNode | null { - // at '(': ('...'? Identifier '?'? ':' Type (',' '...'? Identifier '?'? ':' Type)* )? ')' '=>' Type + // at '(': (Decorator* ('...'? Identifier '?'? ':' Type | this ':' Type) + // (',' Decorator* ('...'? Identifier '?'? ':' Type | this ':' Type))* )? ')' '=>' Type let state = tn.mark(); let startPos = tn.tokenPos; let parameters: ParameterNode[] | null = null; let thisType: NamedTypeNode | null = null; + let thisDecorators: DecoratorNode[] | null = null; let isSignature: bool = false; let firstParamNameNoType: IdentifierExpression | null = null; let firstParamKind: ParameterKind = ParameterKind.Default; @@ -723,6 +725,12 @@ export class Parser extends DiagnosticEmitter { do { let paramStart = -1; let kind = ParameterKind.Default; + let decorators = this.parseParameterDecorators(tn); + if (decorators) { + paramStart = decorators[0].range.start; + isSignature = true; + tn.discard(state); + } if (tn.skip(Token.Dot_Dot_Dot)) { paramStart = tn.tokenPos; isSignature = true; @@ -745,6 +753,7 @@ export class Parser extends DiagnosticEmitter { return null; } thisType = type; + thisDecorators = decorators; } else { tn.reset(state); this.tryParseSignatureIsSignature = false; @@ -773,7 +782,7 @@ export class Parser extends DiagnosticEmitter { this.tryParseSignatureIsSignature = isSignature; return null; } - let param = Node.createParameter(kind, name, type, null, tn.range(paramStart, tn.pos)); + let param = Node.createParameter(kind, name, type, null, tn.range(paramStart, tn.pos), decorators); if (!parameters) parameters = [ param ]; else parameters.push(param); } else { @@ -784,7 +793,7 @@ export class Parser extends DiagnosticEmitter { } } if (isSignature) { - let param = Node.createParameter(kind, name, Node.createOmittedType(tn.range(tn.pos)), null, tn.range(paramStart, tn.pos)); + let param = Node.createParameter(kind, name, Node.createOmittedType(tn.range(tn.pos)), null, tn.range(paramStart, tn.pos), decorators); if (!parameters) parameters = [ param ]; else parameters.push(param); this.error( @@ -869,13 +878,15 @@ export class Parser extends DiagnosticEmitter { if (!parameters) parameters = []; - return Node.createFunctionType( + let functionType = Node.createFunctionType( parameters, returnType, thisType, false, tn.range(startPos, tn.pos) ); + functionType.explicitThisDecorators = thisDecorators; + return functionType; } // statements @@ -924,6 +935,20 @@ export class Parser extends DiagnosticEmitter { return null; } + private parseParameterDecorators( + tn: Tokenizer + ): DecoratorNode[] | null { + // Preserve parameter decorators in the AST so transforms can inspect or remove them later. + let decorators: DecoratorNode[] | null = null; + while (tn.skip(Token.At)) { + let decorator = this.parseDecorator(tn); + if (!decorator) break; + if (!decorators) decorators = [decorator]; + else decorators.push(decorator); + } + return decorators; + } + parseVariable( tn: Tokenizer, flags: CommonFlags, @@ -1226,14 +1251,17 @@ export class Parser extends DiagnosticEmitter { return null; } + /** Explicit `this` parameter captured by the current parseParameters call, if any. */ private parseParametersThis: NamedTypeNode | null = null; + /** Decorators on the explicit `this` parameter, preserved for transforms. */ + private parseParametersThisDecorators: DecoratorNode[] | null = null; parseParameters( tn: Tokenizer, isConstructor: bool = false ): ParameterNode[] | null { - // at '(': (Parameter (',' Parameter)*)? ')' + // at '(': (Decorator* Parameter (',' Decorator* Parameter)*)? ')' let parameters = new Array(); let seenRest: ParameterNode | null = null; @@ -1241,42 +1269,52 @@ export class Parser extends DiagnosticEmitter { let reportedRest = false; let thisType: TypeNode | null = null; - // check if there is a leading `this` parameter + // check if there is a leading `this` parameter, preserving any decorators on it this.parseParametersThis = null; - if (tn.skip(Token.This)) { - if (tn.skip(Token.Colon)) { - thisType = this.parseType(tn); // reports - if (!thisType) return null; - if (thisType.kind == NodeKind.NamedType) { - this.parseParametersThis = thisType; - } else { - this.error( - DiagnosticCode.Identifier_expected, - thisType.range - ); - } - } else { - this.error( - DiagnosticCode._0_expected, - tn.range(), ":" - ); - return null; - } - if (!tn.skip(Token.Comma)) { - if (tn.skip(Token.CloseParen)) { - return parameters; + this.parseParametersThisDecorators = null; + + let first = true; + while (true) { + if (tn.skip(Token.CloseParen)) break; + + let paramDecorators = this.parseParameterDecorators(tn); + + if (first && tn.skip(Token.This)) { + if (tn.skip(Token.Colon)) { + thisType = this.parseType(tn); // reports + if (!thisType) return null; + if (thisType.kind == NodeKind.NamedType) { + this.parseParametersThis = thisType; + this.parseParametersThisDecorators = paramDecorators; + } else { + this.error( + DiagnosticCode.Identifier_expected, + thisType.range + ); + } } else { this.error( DiagnosticCode._0_expected, - tn.range(), ")" + tn.range(), ":" ); return null; } + first = false; + if (!tn.skip(Token.Comma)) { + if (tn.skip(Token.CloseParen)) { + break; + } else { + this.error( + DiagnosticCode._0_expected, + tn.range(), ")" + ); + return null; + } + } + continue; } - } - while (!tn.skip(Token.CloseParen)) { - let param = this.parseParameter(tn, isConstructor); // reports + let param = this.parseParameter(tn, isConstructor, paramDecorators); // reports if (!param) return null; if (seenRest && !reportedRest) { this.error( @@ -1305,6 +1343,7 @@ export class Parser extends DiagnosticEmitter { } } parameters.push(param); + first = false; if (!tn.skip(Token.Comma)) { if (tn.skip(Token.CloseParen)) { break; @@ -1322,24 +1361,28 @@ export class Parser extends DiagnosticEmitter { parseParameter( tn: Tokenizer, - isConstructor: bool = false + isConstructor: bool = false, + decorators: DecoratorNode[] | null = null ): ParameterNode | null { - // before: ('public' | 'private' | 'protected' | '...')? Identifier '?'? (':' Type)? ('=' Expression)? + // before: Decorator* ('public' | 'private' | 'protected' | 'readonly')? '...'? Identifier + // '?'? (':' Type)? ('=' Expression)? let isRest = false; let isOptional = false; - let startRange: Range | null = null; + let startRange: Range | null = decorators + ? decorators[0].range + : null; let accessFlags: CommonFlags = CommonFlags.None; if (isConstructor) { if (tn.skip(Token.Public)) { - startRange = tn.range(); + if (!startRange) startRange = tn.range(); accessFlags |= CommonFlags.Public; } else if (tn.skip(Token.Protected)) { - startRange = tn.range(); + if (!startRange) startRange = tn.range(); accessFlags |= CommonFlags.Protected; } else if (tn.skip(Token.Private)) { - startRange = tn.range(); + if (!startRange) startRange = tn.range(); accessFlags |= CommonFlags.Private; } if (tn.peek() == Token.Readonly) { @@ -1361,12 +1404,12 @@ export class Parser extends DiagnosticEmitter { tn.range() ); } else { - startRange = tn.range(); + if (!startRange) startRange = tn.range(); } isRest = true; } if (tn.skipIdentifier()) { - if (!isRest) startRange = tn.range(); + if (!isRest && !startRange) startRange = tn.range(); let identifier = Node.createIdentifierExpression(tn.readIdentifier(), tn.range()); let type: TypeNode | null = null; if (isOptional = tn.skip(Token.Question)) { @@ -1411,7 +1454,8 @@ export class Parser extends DiagnosticEmitter { identifier, type, initializer, - Range.join(assert(startRange), tn.range()) + Range.join(assert(startRange), tn.range()), + decorators ); param.flags |= accessFlags; return param; @@ -1523,6 +1567,7 @@ export class Parser extends DiagnosticEmitter { false, tn.range(signatureStart, tn.pos) ); + signature.explicitThisDecorators = this.parseParametersThisDecorators; let body: Statement | null = null; if (tn.skip(Token.OpenBrace)) { @@ -1597,7 +1642,16 @@ export class Parser extends DiagnosticEmitter { let parameters = this.parseParameters(tn); if (!parameters) return null; - return this.parseFunctionExpressionCommon(tn, name, parameters, this.parseParametersThis, arrowKind, startPos, signatureStart); + return this.parseFunctionExpressionCommon( + tn, + name, + parameters, + this.parseParametersThis, + this.parseParametersThisDecorators, + arrowKind, + startPos, + signatureStart + ); } private parseFunctionExpressionCommon( @@ -1605,6 +1659,7 @@ export class Parser extends DiagnosticEmitter { name: IdentifierExpression, parameters: ParameterNode[], explicitThis: NamedTypeNode | null, + explicitThisDecorators: DecoratorNode[] | null, arrowKind: ArrowKind, startPos: i32 = -1, signatureStart: i32 = -1 @@ -1637,6 +1692,7 @@ export class Parser extends DiagnosticEmitter { false, tn.range(signatureStart, tn.pos) ); + signature.explicitThisDecorators = explicitThisDecorators; let body: Statement | null = null; if (arrowKind) { @@ -2282,6 +2338,7 @@ export class Parser extends DiagnosticEmitter { false, tn.range(signatureStart, tn.pos) ); + signature.explicitThisDecorators = this.parseParametersThisDecorators; let body: Statement | null = null; if (tn.skip(Token.OpenBrace)) { @@ -3729,6 +3786,7 @@ export class Parser extends DiagnosticEmitter { Node.createEmptyIdentifierExpression(tn.range(startPos)), [], null, + null, ArrowKind.Parenthesized ); } @@ -3738,6 +3796,7 @@ export class Parser extends DiagnosticEmitter { switch (tn.next(IdentifierHandling.Prefer)) { // function expression + case Token.At: case Token.Dot_Dot_Dot: { tn.reset(state); return this.parseFunctionExpression(tn); @@ -3930,6 +3989,7 @@ export class Parser extends DiagnosticEmitter { ) ], null, + null, ArrowKind.Single, startPos ); diff --git a/src/program.ts b/src/program.ts index 8bcf1bfa19..c44887bca1 100644 --- a/src/program.ts +++ b/src/program.ts @@ -86,29 +86,61 @@ import { ArrowKind, Expression, + ArrayLiteralExpression, + AssertionExpression, + BinaryExpression, + CallExpression, + ClassExpression, + CommaExpression, + ElementAccessExpression, + FunctionExpression, IdentifierExpression, + InstanceOfExpression, + LiteralExpression, + NewExpression, + ObjectLiteralExpression, + ParenthesizedExpression, + PropertyAccessExpression, + TernaryExpression, + TemplateLiteralExpression, + UnaryPostfixExpression, + UnaryPrefixExpression, LiteralKind, StringLiteralExpression, Statement, + BlockStatement, ClassDeclaration, DeclarationStatement, + DoStatement, + ExpressionStatement, EnumDeclaration, EnumValueDeclaration, ExportMember, ExportDefaultStatement, ExportStatement, FieldDeclaration, + ForOfStatement, + ForStatement, FunctionDeclaration, + IfStatement, ImportDeclaration, ImportStatement, + IndexSignatureNode, InterfaceDeclaration, MethodDeclaration, NamespaceDeclaration, + ReturnStatement, + SwitchCase, + SwitchStatement, + ThrowStatement, + TryStatement, TypeDeclaration, VariableDeclaration, VariableLikeDeclarationStatement, VariableStatement, + VoidStatement, + WhileStatement, ParameterKind, ParameterNode, TypeName @@ -462,6 +494,8 @@ export class Program extends DiagnosticEmitter { nextSignatureId: i32 = 0; /** An indicator if the program has been initialized. */ initialized: bool = false; + /** Indicates whether the one-shot post-transform parameter decorator validation has run. */ + parameterDecoratorsValidated: bool = false; // Lookup maps @@ -1497,6 +1531,386 @@ export class Program extends DiagnosticEmitter { } } + /** Rejects parameter decorators that survive transform time. These remain transform-only syntax. */ + validateParameterDecorators(): void { + if (this.parameterDecoratorsValidated) return; + this.parameterDecoratorsValidated = true; + let sources = this.sources; + for (let i = 0, k = sources.length; i < k; ++i) { + this.validateParameterDecoratorsInSource(sources[i]); + } + } + + private validateParameterDecoratorsInSource(source: Source): void { + let statements = source.statements; + for (let i = 0, k = statements.length; i < k; ++i) { + this.validateParameterDecoratorsInStatement(statements[i]); + } + } + + private validateParameterDecoratorsInStatements(statements: Statement[] | null): void { + if (statements) { + for (let i = 0, k = statements.length; i < k; ++i) { + this.validateParameterDecoratorsInStatement(statements[i]); + } + } + } + + private validateParameterDecoratorsInStatement(statement: Statement | null): void { + if (!statement) return; + switch (statement.kind) { + case NodeKind.Block: { + this.validateParameterDecoratorsInStatements((statement).statements); + break; + } + case NodeKind.ClassDeclaration: + case NodeKind.InterfaceDeclaration: { + this.validateParameterDecoratorsInClassDeclaration(statement); + break; + } + case NodeKind.Do: { + let doStatement = statement; + this.validateParameterDecoratorsInStatement(doStatement.body); + this.validateParameterDecoratorsInExpression(doStatement.condition); + break; + } + case NodeKind.EnumDeclaration: { + this.validateParameterDecoratorsInEnumDeclaration(statement); + break; + } + case NodeKind.ExportDefault: { + this.validateParameterDecoratorsInDeclaration((statement).declaration); + break; + } + case NodeKind.Expression: { + this.validateParameterDecoratorsInExpression((statement).expression); + break; + } + case NodeKind.For: { + let forStatement = statement; + this.validateParameterDecoratorsInStatement(forStatement.initializer); + this.validateParameterDecoratorsInExpression(forStatement.condition); + this.validateParameterDecoratorsInExpression(forStatement.incrementor); + this.validateParameterDecoratorsInStatement(forStatement.body); + break; + } + case NodeKind.ForOf: { + let forOfStatement = statement; + this.validateParameterDecoratorsInStatement(forOfStatement.variable); + this.validateParameterDecoratorsInExpression(forOfStatement.iterable); + this.validateParameterDecoratorsInStatement(forOfStatement.body); + break; + } + case NodeKind.FunctionDeclaration: + case NodeKind.MethodDeclaration: { + this.validateParameterDecoratorsInFunctionDeclaration(statement); + break; + } + case NodeKind.If: { + let ifStatement = statement; + this.validateParameterDecoratorsInExpression(ifStatement.condition); + this.validateParameterDecoratorsInStatement(ifStatement.ifTrue); + this.validateParameterDecoratorsInStatement(ifStatement.ifFalse); + break; + } + case NodeKind.NamespaceDeclaration: { + this.validateParameterDecoratorsInStatements((statement).members); + break; + } + case NodeKind.Return: { + this.validateParameterDecoratorsInExpression((statement).value); + break; + } + case NodeKind.Switch: { + let switchStatement = statement; + this.validateParameterDecoratorsInExpression(switchStatement.condition); + let cases = switchStatement.cases; + for (let i = 0, k = cases.length; i < k; ++i) { + this.validateParameterDecoratorsInSwitchCase(cases[i]); + } + break; + } + case NodeKind.Throw: { + this.validateParameterDecoratorsInExpression((statement).value); + break; + } + case NodeKind.Try: { + let tryStatement = statement; + this.validateParameterDecoratorsInStatements(tryStatement.bodyStatements); + this.validateParameterDecoratorsInStatements(tryStatement.catchStatements); + this.validateParameterDecoratorsInStatements(tryStatement.finallyStatements); + break; + } + case NodeKind.TypeDeclaration: { + this.validateParameterDecoratorsInTypeDeclaration(statement); + break; + } + case NodeKind.Variable: { + let declarations = (statement).declarations; + for (let i = 0, k = declarations.length; i < k; ++i) { + this.validateParameterDecoratorsInVariableLikeDeclaration(declarations[i]); + } + break; + } + case NodeKind.Void: { + this.validateParameterDecoratorsInExpression((statement).expression); + break; + } + case NodeKind.While: { + let whileStatement = statement; + this.validateParameterDecoratorsInExpression(whileStatement.condition); + this.validateParameterDecoratorsInStatement(whileStatement.body); + break; + } + } + } + + private validateParameterDecoratorsInDeclaration(declaration: DeclarationStatement): void { + switch (declaration.kind) { + case NodeKind.ClassDeclaration: + case NodeKind.InterfaceDeclaration: { + this.validateParameterDecoratorsInClassDeclaration(declaration); + break; + } + case NodeKind.EnumDeclaration: { + this.validateParameterDecoratorsInEnumDeclaration(declaration); + break; + } + case NodeKind.FieldDeclaration: + case NodeKind.VariableDeclaration: { + this.validateParameterDecoratorsInVariableLikeDeclaration(declaration); + break; + } + case NodeKind.FunctionDeclaration: + case NodeKind.MethodDeclaration: { + this.validateParameterDecoratorsInFunctionDeclaration(declaration); + break; + } + case NodeKind.NamespaceDeclaration: { + this.validateParameterDecoratorsInStatements((declaration).members); + break; + } + case NodeKind.TypeDeclaration: { + this.validateParameterDecoratorsInTypeDeclaration(declaration); + break; + } + } + } + + private validateParameterDecoratorsInClassDeclaration(node: ClassDeclaration): void { + this.validateParameterDecoratorsInTypeParameters(node.typeParameters); + this.validateParameterDecoratorsInType(node.extendsType); + let implementsTypes = node.implementsTypes; + if (implementsTypes) { + for (let i = 0, k = implementsTypes.length; i < k; ++i) { + this.validateParameterDecoratorsInType(implementsTypes[i]); + } + } + this.validateParameterDecoratorsInIndexSignature(node.indexSignature); + let members = node.members; + for (let i = 0, k = members.length; i < k; ++i) { + this.validateParameterDecoratorsInDeclaration(members[i]); + } + } + + private validateParameterDecoratorsInEnumDeclaration(node: EnumDeclaration): void { + let values = node.values; + for (let i = 0, k = values.length; i < k; ++i) { + this.validateParameterDecoratorsInVariableLikeDeclaration(values[i]); + } + } + + private validateParameterDecoratorsInFunctionDeclaration(node: FunctionDeclaration): void { + this.validateParameterDecoratorsInTypeParameters(node.typeParameters); + this.validateParameterDecoratorsInFunctionType(node.signature); + this.validateParameterDecoratorsInStatement(node.body); + } + + private validateParameterDecoratorsInTypeDeclaration(node: TypeDeclaration): void { + this.validateParameterDecoratorsInTypeParameters(node.typeParameters); + this.validateParameterDecoratorsInType(node.type); + } + + private validateParameterDecoratorsInVariableLikeDeclaration(node: VariableLikeDeclarationStatement): void { + this.validateParameterDecoratorsInType(node.type); + this.validateParameterDecoratorsInExpression(node.initializer); + } + + private validateParameterDecoratorsInSwitchCase(node: SwitchCase): void { + this.validateParameterDecoratorsInExpression(node.label); + this.validateParameterDecoratorsInStatements(node.statements); + } + + private validateParameterDecoratorsInIndexSignature(node: IndexSignatureNode | null): void { + if (!node) return; + this.validateParameterDecoratorsInType(node.keyType); + this.validateParameterDecoratorsInType(node.valueType); + } + + private validateParameterDecoratorsInExpression(node: Expression | null): void { + if (!node) return; + switch (node.kind) { + case NodeKind.Assertion: { + let assertion = node; + this.validateParameterDecoratorsInExpression(assertion.expression); + this.validateParameterDecoratorsInType(assertion.toType); + break; + } + case NodeKind.Binary: { + let binary = node; + this.validateParameterDecoratorsInExpression(binary.left); + this.validateParameterDecoratorsInExpression(binary.right); + break; + } + case NodeKind.Call: { + let call = node; + this.validateParameterDecoratorsInExpression(call.expression); + this.validateParameterDecoratorsInTypes(call.typeArguments); + this.validateParameterDecoratorsInExpressions(call.args); + break; + } + case NodeKind.Class: { + this.validateParameterDecoratorsInClassDeclaration((node).declaration); + break; + } + case NodeKind.Comma: { + this.validateParameterDecoratorsInExpressions((node).expressions); + break; + } + case NodeKind.ElementAccess: { + let elementAccess = node; + this.validateParameterDecoratorsInExpression(elementAccess.expression); + this.validateParameterDecoratorsInExpression(elementAccess.elementExpression); + break; + } + case NodeKind.Function: { + this.validateParameterDecoratorsInFunctionDeclaration((node).declaration); + break; + } + case NodeKind.InstanceOf: { + let instanceOf = node; + this.validateParameterDecoratorsInExpression(instanceOf.expression); + this.validateParameterDecoratorsInType(instanceOf.isType); + break; + } + case NodeKind.Literal: { + this.validateParameterDecoratorsInLiteral(node); + break; + } + case NodeKind.New: { + let newExpression = node; + this.validateParameterDecoratorsInTypes(newExpression.typeArguments); + this.validateParameterDecoratorsInExpressions(newExpression.args); + break; + } + case NodeKind.Parenthesized: { + this.validateParameterDecoratorsInExpression((node).expression); + break; + } + case NodeKind.PropertyAccess: { + this.validateParameterDecoratorsInExpression((node).expression); + break; + } + case NodeKind.Ternary: { + let ternary = node; + this.validateParameterDecoratorsInExpression(ternary.condition); + this.validateParameterDecoratorsInExpression(ternary.ifThen); + this.validateParameterDecoratorsInExpression(ternary.ifElse); + break; + } + case NodeKind.UnaryPostfix: { + this.validateParameterDecoratorsInExpression((node).operand); + break; + } + case NodeKind.UnaryPrefix: { + this.validateParameterDecoratorsInExpression((node).operand); + break; + } + } + } + + private validateParameterDecoratorsInExpressions(nodes: Expression[] | null): void { + if (nodes) { + for (let i = 0, k = nodes.length; i < k; ++i) { + this.validateParameterDecoratorsInExpression(nodes[i]); + } + } + } + + private validateParameterDecoratorsInLiteral(node: LiteralExpression): void { + switch (node.literalKind) { + case LiteralKind.Array: { + this.validateParameterDecoratorsInExpressions((node).elementExpressions); + break; + } + case LiteralKind.Object: { + this.validateParameterDecoratorsInExpressions((node).values); + break; + } + case LiteralKind.Template: { + this.validateParameterDecoratorsInExpressions((node).expressions); + break; + } + } + } + + private validateParameterDecoratorsInType(node: TypeNode | null): void { + if (!node) return; + switch (node.kind) { + case NodeKind.FunctionType: { + this.validateParameterDecoratorsInFunctionType(node); + break; + } + case NodeKind.NamedType: { + this.validateParameterDecoratorsInTypes((node).typeArguments); + break; + } + } + } + + private validateParameterDecoratorsInTypes(nodes: TypeNode[] | null): void { + if (nodes) { + for (let i = 0, k = nodes.length; i < k; ++i) { + this.validateParameterDecoratorsInType(nodes[i]); + } + } + } + + private validateParameterDecoratorsInTypeParameters(nodes: TypeParameterNode[] | null): void { + if (nodes) { + for (let i = 0, k = nodes.length; i < k; ++i) { + let node = nodes[i]; + this.validateParameterDecoratorsInType(node.extendsType); + this.validateParameterDecoratorsInType(node.defaultType); + } + } + } + + private validateParameterDecoratorsInFunctionType(node: FunctionTypeNode): void { + this.reportParameterDecorators(node.explicitThisDecorators); + this.validateParameterDecoratorsInType(node.explicitThisType); + let parameters = node.parameters; + for (let i = 0, k = parameters.length; i < k; ++i) { + this.validateParameterDecoratorsInParameter(parameters[i]); + } + this.validateParameterDecoratorsInType(node.returnType); + } + + private validateParameterDecoratorsInParameter(node: ParameterNode): void { + this.reportParameterDecorators(node.decorators); + this.validateParameterDecoratorsInType(node.type); + this.validateParameterDecoratorsInExpression(node.initializer); + } + + private reportParameterDecorators(decorators: DecoratorNode[] | null): void { + if (decorators && decorators.length > 0) { + this.error( + DiagnosticCode.Decorators_are_not_valid_here, + Range.join(decorators[0].range, decorators[decorators.length - 1].range) + ); + } + } + /** Processes overridden members by this class in a base class. */ private processOverrides( thisPrototype: ClassPrototype, diff --git a/tests/compiler/parameter-decorators.json b/tests/compiler/parameter-decorators.json new file mode 100644 index 0000000000..8bae20a4ab --- /dev/null +++ b/tests/compiler/parameter-decorators.json @@ -0,0 +1,23 @@ +{ + "asc_flags": [ + ], + "stderr": [ + "TS1206: Decorators are not valid here.", + "function regular(@first value: i32): void {}", + "TS1206: Decorators are not valid here.", + "function rest(@rest ...values: i32[]): void {}", + "TS1206: Decorators are not valid here.", + "function withthis(@self this: i32, value: i32): i32 { return this; }", + "TS1206: Decorators are not valid here.", + "constructor(@field public value: i32) {}", + "TS1206: Decorators are not valid here.", + "method(@arg value: i32): void {}", + "TS1206: Decorators are not valid here.", + "type Callback = (@arg value: i32) => void;", + "TS1206: Decorators are not valid here.", + "const expression = function(@arg value: i32): void {};", + "TS1206: Decorators are not valid here.", + "const arrow = (@arg value: i32): void => {};", + "EOF" + ] +} diff --git a/tests/compiler/parameter-decorators.ts b/tests/compiler/parameter-decorators.ts new file mode 100644 index 0000000000..0a2ec5c8d0 --- /dev/null +++ b/tests/compiler/parameter-decorators.ts @@ -0,0 +1,14 @@ +function regular(@first value: i32): void {} +function rest(@rest ...values: i32[]): void {} +function withthis(@self this: i32, value: i32): i32 { return this; } + +class Box { + constructor(@field public value: i32) {} + method(@arg value: i32): void {} +} + +type Callback = (@arg value: i32) => void; +const expression = function(@arg value: i32): void {}; +const arrow = (@arg value: i32): void => {}; + +ERROR("EOF"); diff --git a/tests/parser/parameter-decorators.ts b/tests/parser/parameter-decorators.ts new file mode 100644 index 0000000000..4dbd1b1571 --- /dev/null +++ b/tests/parser/parameter-decorators.ts @@ -0,0 +1,11 @@ +function regular(@first @second("x") value: i32, @optional maybe?: i32): void {} +function withthis(@self this: i32, @rest ...values: i32[]): i32 { return this; } + +class Box { + constructor(@field public value: i32) {} + method(@arg value: i32): void {} +} + +type Callback = (@self this: i32, @arg value: i32, @rest ...values: i32[]) => void; +const expression = function (@arg value: i32): void {}; +const arrow = (@arg value: i32): void => {}; diff --git a/tests/parser/parameter-decorators.ts.fixture.ts b/tests/parser/parameter-decorators.ts.fixture.ts new file mode 100644 index 0000000000..8ca6033bc0 --- /dev/null +++ b/tests/parser/parameter-decorators.ts.fixture.ts @@ -0,0 +1,11 @@ +function regular(@first @second("x") value: i32, @optional maybe?: i32): void {} +function withthis(@self this: i32, @rest ...values: Array): i32 { + return this; +} +class Box { + constructor(@field public value: i32) {} + method(@arg value: i32): void {} +} +type Callback = (@self this: i32, @arg value: i32, @rest ...values: Array) => void; +const expression = function(@arg value: i32): void {}; +const arrow = (@arg value: i32): void => {}; diff --git a/tests/transform/cjs/remove-parameter-decorators.js b/tests/transform/cjs/remove-parameter-decorators.js new file mode 100644 index 0000000000..2eee0745c3 --- /dev/null +++ b/tests/transform/cjs/remove-parameter-decorators.js @@ -0,0 +1,329 @@ +// Example transform proving that preserved parameter decorators can be stripped +// during afterInitialize before compilation rejects them. +console.log("CommonJS parameter decorator removal transform loaded"); + +const NodeKind = { + NamedType: 1, + FunctionType: 2, + Assertion: 7, + Binary: 8, + Call: 9, + Class: 10, + Comma: 11, + ElementAccess: 12, + Function: 14, + InstanceOf: 15, + Literal: 16, + New: 17, + Parenthesized: 20, + PropertyAccess: 21, + Ternary: 22, + UnaryPostfix: 27, + UnaryPrefix: 28, + Block: 30, + Do: 33, + ExportDefault: 36, + Expression: 38, + For: 39, + ForOf: 40, + If: 41, + Return: 43, + Switch: 44, + Throw: 45, + Try: 46, + Variable: 47, + Void: 48, + While: 49, + ClassDeclaration: 51, + EnumDeclaration: 52, + FieldDeclaration: 54, + FunctionDeclaration: 55, + InterfaceDeclaration: 57, + MethodDeclaration: 58, + NamespaceDeclaration: 59, + TypeDeclaration: 60, + VariableDeclaration: 61 +}; + +const LiteralKind = { + Template: 3, + Array: 5, + Object: 6 +}; + +exports.afterInitialize = (program) => { + console.log("- afterInitialize strip parameter decorators"); + for (const source of program.sources) { + clearStatements(source.statements); + } +}; + +function clearStatements(statements) { + if (!statements) return; + for (const statement of statements) { + clearStatement(statement); + } +} + +function clearStatement(statement) { + if (!statement) return; + switch (statement.kind) { + case NodeKind.Block: + clearStatements(statement.statements); + break; + case NodeKind.ClassDeclaration: + case NodeKind.InterfaceDeclaration: + clearClassDeclaration(statement); + break; + case NodeKind.Do: + clearStatement(statement.body); + clearExpression(statement.condition); + break; + case NodeKind.EnumDeclaration: + for (const value of statement.values) { + clearVariableLike(value); + } + break; + case NodeKind.ExportDefault: + clearDeclaration(statement.declaration); + break; + case NodeKind.Expression: + clearExpression(statement.expression); + break; + case NodeKind.For: + clearStatement(statement.initializer); + clearExpression(statement.condition); + clearExpression(statement.incrementor); + clearStatement(statement.body); + break; + case NodeKind.ForOf: + clearStatement(statement.variable); + clearExpression(statement.iterable); + clearStatement(statement.body); + break; + case NodeKind.FunctionDeclaration: + case NodeKind.MethodDeclaration: + clearFunctionDeclaration(statement); + break; + case NodeKind.If: + clearExpression(statement.condition); + clearStatement(statement.ifTrue); + clearStatement(statement.ifFalse); + break; + case NodeKind.NamespaceDeclaration: + clearStatements(statement.members); + break; + case NodeKind.Return: + clearExpression(statement.value); + break; + case NodeKind.Switch: + clearExpression(statement.condition); + for (const switchCase of statement.cases) { + clearExpression(switchCase.label); + clearStatements(switchCase.statements); + } + break; + case NodeKind.Throw: + clearExpression(statement.value); + break; + case NodeKind.Try: + clearStatements(statement.bodyStatements); + clearStatements(statement.catchStatements); + clearStatements(statement.finallyStatements); + break; + case NodeKind.TypeDeclaration: + clearTypeDeclaration(statement); + break; + case NodeKind.Variable: + for (const declaration of statement.declarations) { + clearVariableLike(declaration); + } + break; + case NodeKind.Void: + clearExpression(statement.expression); + break; + case NodeKind.While: + clearExpression(statement.condition); + clearStatement(statement.body); + break; + } +} + +function clearDeclaration(declaration) { + if (!declaration) return; + switch (declaration.kind) { + case NodeKind.ClassDeclaration: + case NodeKind.InterfaceDeclaration: + clearClassDeclaration(declaration); + break; + case NodeKind.EnumDeclaration: + for (const value of declaration.values) { + clearVariableLike(value); + } + break; + case NodeKind.FieldDeclaration: + case NodeKind.VariableDeclaration: + clearVariableLike(declaration); + break; + case NodeKind.FunctionDeclaration: + case NodeKind.MethodDeclaration: + clearFunctionDeclaration(declaration); + break; + case NodeKind.NamespaceDeclaration: + clearStatements(declaration.members); + break; + case NodeKind.TypeDeclaration: + clearTypeDeclaration(declaration); + break; + } +} + +function clearClassDeclaration(declaration) { + clearTypeParameters(declaration.typeParameters); + clearType(declaration.extendsType); + clearTypes(declaration.implementsTypes); + clearIndexSignature(declaration.indexSignature); + for (const member of declaration.members) { + clearDeclaration(member); + } +} + +function clearFunctionDeclaration(declaration) { + clearTypeParameters(declaration.typeParameters); + clearFunctionType(declaration.signature); + clearStatement(declaration.body); +} + +function clearTypeDeclaration(declaration) { + clearTypeParameters(declaration.typeParameters); + clearType(declaration.type); +} + +function clearVariableLike(declaration) { + clearType(declaration.type); + clearExpression(declaration.initializer); +} + +function clearExpression(expression) { + if (!expression) return; + switch (expression.kind) { + case NodeKind.Assertion: + clearExpression(expression.expression); + clearType(expression.toType); + break; + case NodeKind.Binary: + clearExpression(expression.left); + clearExpression(expression.right); + break; + case NodeKind.Call: + clearExpression(expression.expression); + clearTypes(expression.typeArguments); + clearExpressions(expression.args); + break; + case NodeKind.Class: + clearClassDeclaration(expression.declaration); + break; + case NodeKind.Comma: + clearExpressions(expression.expressions); + break; + case NodeKind.ElementAccess: + clearExpression(expression.expression); + clearExpression(expression.elementExpression); + break; + case NodeKind.Function: + clearFunctionDeclaration(expression.declaration); + break; + case NodeKind.InstanceOf: + clearExpression(expression.expression); + clearType(expression.isType); + break; + case NodeKind.Literal: + clearLiteral(expression); + break; + case NodeKind.New: + clearTypes(expression.typeArguments); + clearExpressions(expression.args); + break; + case NodeKind.Parenthesized: + clearExpression(expression.expression); + break; + case NodeKind.PropertyAccess: + clearExpression(expression.expression); + break; + case NodeKind.Ternary: + clearExpression(expression.condition); + clearExpression(expression.ifThen); + clearExpression(expression.ifElse); + break; + case NodeKind.UnaryPostfix: + case NodeKind.UnaryPrefix: + clearExpression(expression.operand); + break; + } +} + +function clearExpressions(expressions) { + if (!expressions) return; + for (const expression of expressions) { + clearExpression(expression); + } +} + +function clearLiteral(literal) { + switch (literal.literalKind) { + case LiteralKind.Array: + clearExpressions(literal.elementExpressions); + break; + case LiteralKind.Object: + clearExpressions(literal.values); + break; + case LiteralKind.Template: + clearExpressions(literal.expressions); + break; + } +} + +function clearType(type) { + if (!type) return; + switch (type.kind) { + case NodeKind.NamedType: + clearTypes(type.typeArguments); + break; + case NodeKind.FunctionType: + clearFunctionType(type); + break; + } +} + +function clearTypes(types) { + if (!types) return; + for (const type of types) { + clearType(type); + } +} + +function clearTypeParameters(typeParameters) { + if (!typeParameters) return; + for (const typeParameter of typeParameters) { + clearType(typeParameter.extendsType); + clearType(typeParameter.defaultType); + } +} + +function clearIndexSignature(indexSignature) { + if (!indexSignature) return; + clearType(indexSignature.keyType); + clearType(indexSignature.valueType); +} + +function clearFunctionType(signature) { + if (!signature) return; + signature.explicitThisDecorators = null; + clearType(signature.explicitThisType); + for (const parameter of signature.parameters) { + parameter.decorators = null; + clearType(parameter.type); + clearExpression(parameter.initializer); + } + clearType(signature.returnType); +} diff --git a/tests/transform/parameter-decorators.ts b/tests/transform/parameter-decorators.ts new file mode 100644 index 0000000000..c08652cfb4 --- /dev/null +++ b/tests/transform/parameter-decorators.ts @@ -0,0 +1,11 @@ +function regular(@first value: i32): void {} +function withthis(@self this: i32, @rest ...values: i32[]): i32 { return this; } + +class Box { + constructor(@field public value: i32) {} + method(@arg value: i32): void {} +} + +type Callback = (@arg value: i32) => void; +const expression = function(@arg value: i32): void {}; +const arrow = (@arg value: i32): void => {}; diff --git a/tests/transform/remove-parameter-decorators.js b/tests/transform/remove-parameter-decorators.js new file mode 100644 index 0000000000..046cc5b67e --- /dev/null +++ b/tests/transform/remove-parameter-decorators.js @@ -0,0 +1,329 @@ +// Example transform proving that preserved parameter decorators can be stripped +// during afterInitialize before compilation rejects them. +console.log("Parameter decorator removal transform loaded"); + +const NodeKind = { + NamedType: 1, + FunctionType: 2, + Assertion: 7, + Binary: 8, + Call: 9, + Class: 10, + Comma: 11, + ElementAccess: 12, + Function: 14, + InstanceOf: 15, + Literal: 16, + New: 17, + Parenthesized: 20, + PropertyAccess: 21, + Ternary: 22, + UnaryPostfix: 27, + UnaryPrefix: 28, + Block: 30, + Do: 33, + ExportDefault: 36, + Expression: 38, + For: 39, + ForOf: 40, + If: 41, + Return: 43, + Switch: 44, + Throw: 45, + Try: 46, + Variable: 47, + Void: 48, + While: 49, + ClassDeclaration: 51, + EnumDeclaration: 52, + FieldDeclaration: 54, + FunctionDeclaration: 55, + InterfaceDeclaration: 57, + MethodDeclaration: 58, + NamespaceDeclaration: 59, + TypeDeclaration: 60, + VariableDeclaration: 61 +}; + +const LiteralKind = { + Template: 3, + Array: 5, + Object: 6 +}; + +export function afterInitialize(program) { + console.log("- afterInitialize strip parameter decorators"); + for (const source of program.sources) { + clearStatements(source.statements); + } +} + +function clearStatements(statements) { + if (!statements) return; + for (const statement of statements) { + clearStatement(statement); + } +} + +function clearStatement(statement) { + if (!statement) return; + switch (statement.kind) { + case NodeKind.Block: + clearStatements(statement.statements); + break; + case NodeKind.ClassDeclaration: + case NodeKind.InterfaceDeclaration: + clearClassDeclaration(statement); + break; + case NodeKind.Do: + clearStatement(statement.body); + clearExpression(statement.condition); + break; + case NodeKind.EnumDeclaration: + for (const value of statement.values) { + clearVariableLike(value); + } + break; + case NodeKind.ExportDefault: + clearDeclaration(statement.declaration); + break; + case NodeKind.Expression: + clearExpression(statement.expression); + break; + case NodeKind.For: + clearStatement(statement.initializer); + clearExpression(statement.condition); + clearExpression(statement.incrementor); + clearStatement(statement.body); + break; + case NodeKind.ForOf: + clearStatement(statement.variable); + clearExpression(statement.iterable); + clearStatement(statement.body); + break; + case NodeKind.FunctionDeclaration: + case NodeKind.MethodDeclaration: + clearFunctionDeclaration(statement); + break; + case NodeKind.If: + clearExpression(statement.condition); + clearStatement(statement.ifTrue); + clearStatement(statement.ifFalse); + break; + case NodeKind.NamespaceDeclaration: + clearStatements(statement.members); + break; + case NodeKind.Return: + clearExpression(statement.value); + break; + case NodeKind.Switch: + clearExpression(statement.condition); + for (const switchCase of statement.cases) { + clearExpression(switchCase.label); + clearStatements(switchCase.statements); + } + break; + case NodeKind.Throw: + clearExpression(statement.value); + break; + case NodeKind.Try: + clearStatements(statement.bodyStatements); + clearStatements(statement.catchStatements); + clearStatements(statement.finallyStatements); + break; + case NodeKind.TypeDeclaration: + clearTypeDeclaration(statement); + break; + case NodeKind.Variable: + for (const declaration of statement.declarations) { + clearVariableLike(declaration); + } + break; + case NodeKind.Void: + clearExpression(statement.expression); + break; + case NodeKind.While: + clearExpression(statement.condition); + clearStatement(statement.body); + break; + } +} + +function clearDeclaration(declaration) { + if (!declaration) return; + switch (declaration.kind) { + case NodeKind.ClassDeclaration: + case NodeKind.InterfaceDeclaration: + clearClassDeclaration(declaration); + break; + case NodeKind.EnumDeclaration: + for (const value of declaration.values) { + clearVariableLike(value); + } + break; + case NodeKind.FieldDeclaration: + case NodeKind.VariableDeclaration: + clearVariableLike(declaration); + break; + case NodeKind.FunctionDeclaration: + case NodeKind.MethodDeclaration: + clearFunctionDeclaration(declaration); + break; + case NodeKind.NamespaceDeclaration: + clearStatements(declaration.members); + break; + case NodeKind.TypeDeclaration: + clearTypeDeclaration(declaration); + break; + } +} + +function clearClassDeclaration(declaration) { + clearTypeParameters(declaration.typeParameters); + clearType(declaration.extendsType); + clearTypes(declaration.implementsTypes); + clearIndexSignature(declaration.indexSignature); + for (const member of declaration.members) { + clearDeclaration(member); + } +} + +function clearFunctionDeclaration(declaration) { + clearTypeParameters(declaration.typeParameters); + clearFunctionType(declaration.signature); + clearStatement(declaration.body); +} + +function clearTypeDeclaration(declaration) { + clearTypeParameters(declaration.typeParameters); + clearType(declaration.type); +} + +function clearVariableLike(declaration) { + clearType(declaration.type); + clearExpression(declaration.initializer); +} + +function clearExpression(expression) { + if (!expression) return; + switch (expression.kind) { + case NodeKind.Assertion: + clearExpression(expression.expression); + clearType(expression.toType); + break; + case NodeKind.Binary: + clearExpression(expression.left); + clearExpression(expression.right); + break; + case NodeKind.Call: + clearExpression(expression.expression); + clearTypes(expression.typeArguments); + clearExpressions(expression.args); + break; + case NodeKind.Class: + clearClassDeclaration(expression.declaration); + break; + case NodeKind.Comma: + clearExpressions(expression.expressions); + break; + case NodeKind.ElementAccess: + clearExpression(expression.expression); + clearExpression(expression.elementExpression); + break; + case NodeKind.Function: + clearFunctionDeclaration(expression.declaration); + break; + case NodeKind.InstanceOf: + clearExpression(expression.expression); + clearType(expression.isType); + break; + case NodeKind.Literal: + clearLiteral(expression); + break; + case NodeKind.New: + clearTypes(expression.typeArguments); + clearExpressions(expression.args); + break; + case NodeKind.Parenthesized: + clearExpression(expression.expression); + break; + case NodeKind.PropertyAccess: + clearExpression(expression.expression); + break; + case NodeKind.Ternary: + clearExpression(expression.condition); + clearExpression(expression.ifThen); + clearExpression(expression.ifElse); + break; + case NodeKind.UnaryPostfix: + case NodeKind.UnaryPrefix: + clearExpression(expression.operand); + break; + } +} + +function clearExpressions(expressions) { + if (!expressions) return; + for (const expression of expressions) { + clearExpression(expression); + } +} + +function clearLiteral(literal) { + switch (literal.literalKind) { + case LiteralKind.Array: + clearExpressions(literal.elementExpressions); + break; + case LiteralKind.Object: + clearExpressions(literal.values); + break; + case LiteralKind.Template: + clearExpressions(literal.expressions); + break; + } +} + +function clearType(type) { + if (!type) return; + switch (type.kind) { + case NodeKind.NamedType: + clearTypes(type.typeArguments); + break; + case NodeKind.FunctionType: + clearFunctionType(type); + break; + } +} + +function clearTypes(types) { + if (!types) return; + for (const type of types) { + clearType(type); + } +} + +function clearTypeParameters(typeParameters) { + if (!typeParameters) return; + for (const typeParameter of typeParameters) { + clearType(typeParameter.extendsType); + clearType(typeParameter.defaultType); + } +} + +function clearIndexSignature(indexSignature) { + if (!indexSignature) return; + clearType(indexSignature.keyType); + clearType(indexSignature.valueType); +} + +function clearFunctionType(signature) { + if (!signature) return; + signature.explicitThisDecorators = null; + clearType(signature.explicitThisType); + for (const parameter of signature.parameters) { + parameter.decorators = null; + clearType(parameter.type); + clearExpression(parameter.initializer); + } + clearType(signature.returnType); +}