From da26d27a181d18ff615cd078459b0d4847c5faee Mon Sep 17 00:00:00 2001 From: TyrealHu Date: Tue, 5 Dec 2023 20:25:50 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=F0=9F=90=9Bthe=20scope=20flag=20between?= =?UTF-8?q?=20type=20value=20and=20ident?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- __test__/__snapshot__/identifier/normal.ts | 263 ++++++++++++++++----- __test__/identifier/normal.test.ts | 8 + src/index.ts | 175 +++++++++++++- src/middleware.ts | 7 + 4 files changed, 382 insertions(+), 71 deletions(-) diff --git a/__test__/__snapshot__/identifier/normal.ts b/__test__/__snapshot__/identifier/normal.ts index 9f1f542..65f041a 100644 --- a/__test__/__snapshot__/identifier/normal.ts +++ b/__test__/__snapshot__/identifier/normal.ts @@ -1,167 +1,314 @@ const NormalIdentifierSnapshot = { + Issue50: { + 'type': 'Program', + 'start': 0, + 'end': 25, + 'loc': { + 'start': { + 'line': 1, + 'column': 0, + 'index': 0 + }, + 'end': { + 'line': 2, + 'column': 8, + 'index': 25 + } + }, + 'body': [ + { + 'type': 'TSTypeAliasDeclaration', + 'start': 0, + 'end': 16, + 'loc': { + 'start': { + 'line': 1, + 'column': 0, + 'index': 0 + }, + 'end': { + 'line': 1, + 'column': 16, + 'index': 16 + } + }, + 'id': { + 'type': 'Identifier', + 'start': 5, + 'end': 8, + 'loc': { + 'start': { + 'line': 1, + 'column': 5, + 'index': 5 + }, + 'end': { + 'line': 1, + 'column': 8, + 'index': 8 + } + }, + 'name': 'abc' + }, + 'typeAnnotation': { + 'type': 'TSLiteralType', + 'start': 11, + 'end': 15, + 'loc': { + 'start': { + 'line': 1, + 'column': 11, + 'index': 11 + }, + 'end': { + 'line': 1, + 'column': 15, + 'index': 15 + } + }, + 'literal': { + 'type': 'Literal', + 'start': 11, + 'end': 15, + 'loc': { + 'start': { + 'line': 1, + 'column': 11, + 'index': 11 + }, + 'end': { + 'line': 1, + 'column': 15, + 'index': 15 + } + }, + 'value': 1234, + 'raw': '1234' + } + } + }, + { + 'type': 'VariableDeclaration', + 'start': 17, + 'end': 25, + 'loc': { + 'start': { + 'line': 2, + 'column': 0, + 'index': 17 + }, + 'end': { + 'line': 2, + 'column': 8, + 'index': 25 + } + }, + 'declarations': [ + { + 'type': 'VariableDeclarator', + 'start': 21, + 'end': 24, + 'loc': { + 'start': { + 'line': 2, + 'column': 4, + 'index': 21 + }, + 'end': { + 'line': 2, + 'column': 7, + 'index': 24 + } + }, + 'id': { + 'type': 'Identifier', + 'start': 21, + 'end': 24, + 'loc': { + 'start': { + 'line': 2, + 'column': 4, + 'index': 21 + }, + 'end': { + 'line': 2, + 'column': 7, + 'index': 24 + } + }, + 'name': 'abc' + }, + 'init': null + } + ], + 'kind': 'var' + } + ], + 'sourceType': 'module' + }, ExportIdentifierAs: { - type: "Program", + type: 'Program', start: 0, end: 34, loc: { start: { line: 1, column: 0, index: 0 }, - end: { line: 2, column: 21, index: 34 }, + end: { line: 2, column: 21, index: 34 } }, body: [ { - type: "VariableDeclaration", + type: 'VariableDeclaration', start: 0, end: 12, loc: { start: { line: 1, column: 0, index: 0 }, - end: { line: 1, column: 12, index: 12 }, + end: { line: 1, column: 12, index: 12 } }, declarations: [ { - type: "VariableDeclarator", + type: 'VariableDeclarator', start: 4, end: 11, loc: { start: { line: 1, column: 4, index: 4 }, - end: { line: 1, column: 11, index: 11 }, + end: { line: 1, column: 11, index: 11 } }, id: { - type: "Identifier", + type: 'Identifier', start: 4, end: 7, loc: { start: { line: 1, column: 4, index: 4 }, - end: { line: 1, column: 7, index: 7 }, + end: { line: 1, column: 7, index: 7 } }, - name: "foo", + name: 'foo' }, init: { - type: "Literal", + type: 'Literal', start: 10, end: 11, loc: { start: { line: 1, column: 10, index: 10 }, - end: { line: 1, column: 11, index: 11 }, + end: { line: 1, column: 11, index: 11 } }, value: 8, - raw: "8", - }, - }, + raw: '8' + } + } ], - kind: "var", + kind: 'var' }, { - type: "ExportNamedDeclaration", + type: 'ExportNamedDeclaration', start: 13, end: 34, loc: { start: { line: 2, column: 0, index: 13 }, - end: { line: 2, column: 21, index: 34 }, + end: { line: 2, column: 21, index: 34 } }, - exportKind: "value", + exportKind: 'value', declaration: null, specifiers: [ { - type: "ExportSpecifier", + type: 'ExportSpecifier', start: 22, end: 31, loc: { start: { line: 2, column: 9, index: 22 }, - end: { line: 2, column: 18, index: 31 }, + end: { line: 2, column: 18, index: 31 } }, local: { - type: "Identifier", + type: 'Identifier', start: 22, end: 25, loc: { start: { line: 2, column: 9, index: 22 }, - end: { line: 2, column: 12, index: 25 }, + end: { line: 2, column: 12, index: 25 } }, - name: "foo", + name: 'foo' }, - exportKind: "value", + exportKind: 'value', exported: { - type: "Identifier", + type: 'Identifier', start: 29, end: 31, loc: { start: { line: 2, column: 16, index: 29 }, - end: { line: 2, column: 18, index: 31 }, + end: { line: 2, column: 18, index: 31 } }, - name: "as", - }, - }, + name: 'as' + } + } ], - source: null, - }, + source: null + } ], - sourceType: "module", + sourceType: 'module' }, ImportIdentifierAs: { - type: "Program", + type: 'Program', start: 0, end: 36, loc: { start: { line: 1, column: 0, index: 0 }, - end: { line: 1, column: 36, index: 36 }, + end: { line: 1, column: 36, index: 36 } }, body: [ { - type: "ImportDeclaration", + type: 'ImportDeclaration', start: 0, end: 36, loc: { start: { line: 1, column: 0, index: 0 }, - end: { line: 1, column: 36, index: 36 }, + end: { line: 1, column: 36, index: 36 } }, - importKind: "value", + importKind: 'value', specifiers: [ { - type: "ImportSpecifier", + type: 'ImportSpecifier', start: 9, end: 17, loc: { start: { line: 1, column: 9, index: 9 }, - end: { line: 1, column: 17, index: 17 }, + end: { line: 1, column: 17, index: 17 } }, imported: { - type: "Identifier", + type: 'Identifier', start: 9, end: 11, loc: { start: { line: 1, column: 9, index: 9 }, - end: { line: 1, column: 11, index: 11 }, + end: { line: 1, column: 11, index: 11 } }, - name: "as", + name: 'as' }, - importKind: "value", + importKind: 'value', local: { - type: "Identifier", + type: 'Identifier', start: 15, end: 17, loc: { start: { line: 1, column: 15, index: 15 }, - end: { line: 1, column: 17, index: 17 }, + end: { line: 1, column: 17, index: 17 } }, - name: "as", - }, - }, + name: 'as' + } + } ], source: { - type: "Literal", + type: 'Literal', start: 25, end: 35, loc: { start: { line: 1, column: 25, index: 25 }, - end: { line: 1, column: 35, index: 35 }, + end: { line: 1, column: 35, index: 35 } }, - value: "./foo.js", - raw: "'./foo.js'", - }, - }, + value: './foo.js', + raw: '\'./foo.js\'' + } + } ], - sourceType: "module", - }, -}; + sourceType: 'module' + } +} -export default NormalIdentifierSnapshot; +export default NormalIdentifierSnapshot diff --git a/__test__/identifier/normal.test.ts b/__test__/identifier/normal.test.ts index 127169d..d716a14 100644 --- a/__test__/identifier/normal.test.ts +++ b/__test__/identifier/normal.test.ts @@ -17,4 +17,12 @@ describe("normal identifier test", () => { equalNode(node, NormalIdentifierSnapshot.ImportIdentifierAs); }); + + it('issue 50', () => { + const node = parseSource( + generateSource([`type abc = 1234;`, `var abc;`]) + ); + + equalNode(node, NormalIdentifierSnapshot.Issue50); + }) }); diff --git a/src/index.ts b/src/index.ts index 1235a21..89bb2ef 100644 --- a/src/index.ts +++ b/src/index.ts @@ -11,11 +11,6 @@ import { TsModifier } from './types' import { - BIND_LEXICAL, - BIND_NONE, - BIND_TS_INTERFACE, - BIND_TS_NAMESPACE, - BIND_TS_TYPE, SCOPE_ARROW, TS_SCOPE_OTHER, TS_SCOPE_TS_MODULE } from './scopeflags' @@ -73,7 +68,17 @@ const acornScope = { BIND_LEXICAL: 2, // Let- or const-style binding BIND_FUNCTION: 3, // Function declaration BIND_SIMPLE_CATCH: 4, // Simple (identifier pattern) catch binding - BIND_OUTSIDE: 5 // Special case for function names as bound inside the + BIND_OUTSIDE: 5, // Special case for function names as bound inside the + BIND_TS_TYPE: 6, + BIND_TS_INTERFACE: 7, + BIND_TS_NAMESPACE: 8, + BIND_FLAGS_TS_EXPORT_ONLY: 0b00010000_0000_00, + BIND_FLAGS_TS_IMPORT: 0b01000000_0000_00, + BIND_KIND_VALUE: 0, + BIND_KIND_TYPE: 0, + BIND_FLAGS_TS_ENUM: 0b00000100_0000_00, + BIND_FLAGS_TS_CONST_ENUM: 0b00001000_0000_00, + BIND_FLAGS_CLASS: 0b00000010_0000_00 // function } @@ -220,6 +225,7 @@ function tsPlugin(options?: { shouldParseArrowReturnType: any | undefined = undefined shouldParseAsyncArrowReturnType: any | undefined = undefined decoratorStack: any[] = [[]] + importsStack: any[] = [[]] /** * we will only parse one import node or export node at same time. * default kind is undefined @@ -235,6 +241,7 @@ function tsPlugin(options?: { constructor(options: Options, input: string, startPos?: number) { super(options, input, startPos) + } // support in Class static @@ -2523,7 +2530,7 @@ function tsPlugin(options?: { if (properties.declare) node.declare = true if (tokenIsIdentifier(this.type)) { node.id = this.parseIdent() - this.checkLValSimple(node.id, BIND_TS_INTERFACE) + this.checkLValSimple(node.id, acornScope.BIND_TS_INTERFACE) } else { node.id = null this.raise(this.start, TypeScriptError.MissingInterfaceName) @@ -2683,7 +2690,7 @@ function tsPlugin(options?: { if (!nested) { - this.checkLValSimple(node.id, BIND_TS_NAMESPACE) + this.checkLValSimple(node.id, acornScope.BIND_TS_NAMESPACE) } if (this.eat(tt.dot)) { @@ -2701,11 +2708,15 @@ function tsPlugin(options?: { return this.finishNode(node, 'TSModuleDeclaration') } + checkLValSimple(expr: any, bindingType: any = acornScope.BIND_NONE, checkClashes?: any) { + return super.checkLValSimple(expr, bindingType, checkClashes) + } + tsParseTypeAliasDeclaration( node: any ): any { node.id = this.parseIdent() - this.checkLValSimple(node.id, BIND_TS_TYPE) + this.checkLValSimple(node.id, acornScope.BIND_TS_TYPE) node.typeAnnotation = this.tsInType(() => { node.typeParameters = this.tsTryParseTypeParameters( this.tsParseInOutModifiers.bind(this) @@ -2790,7 +2801,7 @@ function tsPlugin(options?: { ): Node { node.isExport = isExport || false node.id = this.parseIdent() - this.checkLValSimple(node.id, BIND_LEXICAL) + this.checkLValSimple(node.id, acornScope.BIND_LEXICAL) super.expect(tt.eq) const moduleReference = this.tsParseModuleReference() if ( @@ -4047,7 +4058,7 @@ function tsPlugin(options?: { let oldYieldPos = this.yieldPos, oldAwaitPos = this.awaitPos, oldAwaitIdentPos = this.awaitIdentPos - this.enterScope(functionFlags(isAsync, false) | SCOPE_ARROW) + this.enterScope(functionFlags(isAsync, false) | acornScope.SCOPE_ARROW) this.initFunction(node) const oldMaybeInArrowParameters = this.maybeInArrowParameters if (this.options.ecmaVersion >= 8) node.async = !!isAsync @@ -4319,7 +4330,7 @@ function tsPlugin(options?: { return elt }// AssignmentPattern - checkLValInnerPattern(expr, bindingType = BIND_NONE, checkClashes) { + checkLValInnerPattern(expr, bindingType = acornScope.BIND_NONE, checkClashes) { switch (expr.type) { case 'TSParameterProperty': this.checkLValInnerPattern(expr.parameter, bindingType, checkClashes) @@ -5238,7 +5249,7 @@ function tsPlugin(options?: { node[rightOfAsKey] = this.copyNode(node[leftOfAsKey]) } if (isImport) { - this.checkLValSimple(node[rightOfAsKey], BIND_LEXICAL) + this.checkLValSimple(node[rightOfAsKey], acornScope.BIND_LEXICAL) } } @@ -5308,6 +5319,144 @@ function tsPlugin(options?: { this.expect(tokTypes.jsxTagEnd) return this.finishNode(node, nodeName ? 'JSXOpeningElement' : 'JSXOpeningFragment') } + + enterScope(flags: any) { + if (flags === TS_SCOPE_TS_MODULE) { + this.importsStack.push([]) + } + + super.enterScope(flags) + const scope = super.currentScope() + + scope.types = [] + + scope.enums = [] + + scope.constEnums = [] + + scope.classes = [] + + scope.exportOnlyBindings = [] + } + + exitScope() { + const scope = super.currentScope() + + if (scope.flags === TS_SCOPE_TS_MODULE) { + this.importsStack.pop() + } + + super.exitScope() + } + + hasImport(name: string, allowShadow?: boolean) { + const len = this.importsStack.length; + if (this.importsStack[len - 1].indexOf(name) > -1) { + return true; + } + if (!allowShadow && len > 1) { + for (let i = 0; i < len - 1; i++) { + if (this.importsStack[i].indexOf(name) > -1) return true; + } + } + return false; + } + + maybeExportDefined(scope: any, name: string) { + if (this.inModule && scope.flags & acornScope.SCOPE_TOP) { + this.undefinedExports.delete(name); + } + } + + isRedeclaredInScope( + scope: any, + name: string, + bindingType: any, + ): boolean { + if (!(bindingType & acornScope.BIND_KIND_VALUE)) return false; + + if (bindingType & acornScope.BIND_LEXICAL) { + return ( + scope.lexical.indexOf(name) > -1 || + scope.functions.indexOf(name) > -1 || + scope.var.indexOf(name) > -1 + ); + } + + if (bindingType & acornScope.BIND_FUNCTION) { + return ( + scope.lexical.indexOf(name) > -1 || + (!super.treatFunctionsAsVarInScope(scope) && scope.var.indexOf(name) > -1) + ); + } + + return ( + (scope.lexical.indexOf(name) > -1 && + // Annex B.3.4 + // https://tc39.es/ecma262/#sec-variablestatements-in-catch-blocks + !( + scope.flags & acornScope.SCOPE_SIMPLE_CATCH && + scope.lexical[0] === name + )) || + (!this.treatFunctionsAsVarInScope(scope) && scope.functions.indexOf(name) > -1) + ); + } + + checkRedeclarationInScope( + scope: any, + name: string, + bindingType: any, + loc: any, + ) { + if (this.isRedeclaredInScope(scope, name, bindingType)) { + this.raise(loc, `Identifier '${name}' has already been declared.`); + } + } + + declareName(name, bindingType, pos) { + if (bindingType & acornScope.BIND_FLAGS_TS_IMPORT) { + if (this.hasImport(name, true)) { + this.raise(pos, `Identifier '${name}' has already been declared.`); + } + this.importsStack[this.importsStack.length - 1].push(name); + return; + } + + const scope = this.currentScope(); + if (bindingType & acornScope.BIND_FLAGS_TS_EXPORT_ONLY) { + this.maybeExportDefined(scope, name); + scope.exportOnlyBindings.push(name); + return; + } + + super.declareName(name, bindingType, pos); + + if (bindingType & acornScope.BIND_KIND_TYPE) { + if (!(bindingType & acornScope.BIND_KIND_VALUE)) { + // "Value" bindings have already been registered by the superclass. + this.checkRedeclarationInScope(scope, name, bindingType, pos); + this.maybeExportDefined(scope, name); + } + scope.types.push(name); + } + if (bindingType & acornScope.BIND_FLAGS_TS_ENUM) scope.enums.push(name); + if (bindingType & acornScope.BIND_FLAGS_TS_CONST_ENUM) scope.constEnums.push(name); + if (bindingType & acornScope.BIND_FLAGS_CLASS) scope.classes.push(name); + } + + checkLocalExport(id) { + const { name } = id; + + if (this.hasImport(name)) return; + + const len = this.scopeStack.length; + for (let i = len - 1; i >= 0; i--) { + const scope = this.scopeStack[i]; + if (scope.types.indexOf(name) > -1 || scope.exportOnlyBindings.indexOf(name) > -1) return; + } + + super.checkLocalExport(id); + } } return TypeScriptParser diff --git a/src/middleware.ts b/src/middleware.ts index 8933b32..6d048f4 100644 --- a/src/middleware.ts +++ b/src/middleware.ts @@ -22,6 +22,7 @@ export declare class AcornParseClass extends Parser { exprAllowed: boolean labels: any[] scopeStack: any[] + inModule: any undefinedExports: any lastTokEndLoc: any lastTokStartLoc: any @@ -34,6 +35,12 @@ export declare class AcornParseClass extends Parser { parseImport(node: Node): any; + currentScope(): any + + treatFunctionsAsVarInScope(scope: any): boolean + + declareName(name?: any, bindingType?: any, loc?: any): any + parseImportSpecifier(): any parseExport(node: Node, exports: any): any