diff --git a/src/compiler/binder.ts b/src/compiler/binder.ts index 2557e3b707827..d470a9d778509 100644 --- a/src/compiler/binder.ts +++ b/src/compiler/binder.ts @@ -118,6 +118,7 @@ namespace ts { let thisParentContainer: Node; // Container one level up let blockScopeContainer: Node; let lastContainer: Node; + let delayedTypedefs: { typedef: JSDocTypedefTag, container: Node, lastContainer: Node, blockScopeContainer: Node, parent: Node }[]; let seenThisKeyword: boolean; // state used by control flow analysis @@ -176,6 +177,7 @@ namespace ts { bind(file); file.symbolCount = symbolCount; file.classifiableNames = classifiableNames; + delayedBindJSDocTypedefTag(); } file = undefined; @@ -186,6 +188,7 @@ namespace ts { thisParentContainer = undefined; blockScopeContainer = undefined; lastContainer = undefined; + delayedTypedefs = undefined; seenThisKeyword = false; currentFlow = undefined; currentBreakTarget = undefined; @@ -450,8 +453,7 @@ namespace ts { // and this case is specially handled. Module augmentations should only be merged with original module definition // and should never be merged directly with other augmentation, and the latter case would be possible if automatic merge is allowed. if (node.kind === SyntaxKind.JSDocTypedefTag) Debug.assert(isInJavaScriptFile(node)); // We shouldn't add symbols for JSDoc nodes if not in a JS file. - const isJSDocTypedefInJSDocNamespace = isJSDocTypedefTag(node) && node.name && node.name.kind === SyntaxKind.Identifier && node.name.isInJSDocNamespace; - if ((!isAmbientModule(node) && (hasExportModifier || container.flags & NodeFlags.ExportContext)) || isJSDocTypedefInJSDocNamespace) { + if ((!isAmbientModule(node) && (hasExportModifier || container.flags & NodeFlags.ExportContext)) || isJSDocTypedefTag(node)) { if (hasModifier(node, ModifierFlags.Default) && !getDeclarationName(node)) { return declareSymbol(container.symbol.exports, container.symbol, node, symbolFlags, symbolExcludes); // No local symbol for an unnamed default! } @@ -1727,7 +1729,7 @@ namespace ts { declareModuleMember(node, symbolFlags, symbolExcludes); break; case SyntaxKind.SourceFile: - if (isExternalModule(container)) { + if (isExternalOrCommonJsModule(container)) { declareModuleMember(node, symbolFlags, symbolExcludes); break; } @@ -1745,6 +1747,24 @@ namespace ts { bindBlockScopedDeclaration(node, SymbolFlags.BlockScopedVariable, SymbolFlags.BlockScopedVariableExcludes); } + function delayedBindJSDocTypedefTag() { + if (!delayedTypedefs) { + return; + } + const saveContainer = container; + const saveLastContainer = lastContainer; + const saveBlockScopeContainer = blockScopeContainer; + const saveParent = parent; + for (const delay of delayedTypedefs) { + ({ container, lastContainer, blockScopeContainer, parent } = delay); + bindBlockScopedDeclaration(delay.typedef, SymbolFlags.TypeAlias, SymbolFlags.TypeAliasExcludes); + } + container = saveContainer; + lastContainer = saveLastContainer; + blockScopeContainer = saveBlockScopeContainer; + parent = saveParent; + } + // The binder visits every node in the syntax tree so it is a convenient place to perform a single localized // check for reserved words used as identifiers in strict mode code. function checkStrictModeIdentifier(node: Identifier) { @@ -2194,7 +2214,7 @@ namespace ts { case SyntaxKind.JSDocTypedefTag: { const { fullName } = node as JSDocTypedefTag; if (!fullName || fullName.kind === SyntaxKind.Identifier) { - return bindBlockScopedDeclaration(node, SymbolFlags.TypeAlias, SymbolFlags.TypeAliasExcludes); + (delayedTypedefs || (delayedTypedefs = [])).push({ typedef: node as JSDocTypedefTag, container, lastContainer, blockScopeContainer, parent }); } break; } @@ -2304,7 +2324,10 @@ namespace ts { return s; }); if (symbol) { - declareSymbol(symbol.exports, symbol, lhs, SymbolFlags.Property | SymbolFlags.ExportValue, SymbolFlags.None); + const flags = isClassExpression(node.right) ? + SymbolFlags.Property | SymbolFlags.ExportValue | SymbolFlags.Class : + SymbolFlags.Property | SymbolFlags.ExportValue; + declareSymbol(symbol.exports, symbol, lhs, flags, SymbolFlags.None); } } diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 4886fc98e9a56..3bee345538d42 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -7626,23 +7626,38 @@ namespace ts { return unknownType; } - // A jsdoc TypeReference may have resolved to a value (as opposed to a type). If - // the symbol is a constructor function, return the inferred class type; otherwise, - // the type of this reference is just the type of the value we resolved to. + const jsdocType = getJSDocTypeReference(node, symbol, typeArguments); + if (jsdocType) { + return jsdocType; + } + + // Resolve the type reference as a Type for the purpose of reporting errors. + resolveTypeReferenceName(getTypeReferenceName(node), SymbolFlags.Type); + return getTypeOfSymbol(symbol); + } + + /** + * A jsdoc TypeReference may have resolved to a value (as opposed to a type). If + * the symbol is a constructor function, return the inferred class type; otherwise, + * the type of this reference is just the type of the value we resolved to. + */ + function getJSDocTypeReference(node: NodeWithTypeArguments, symbol: Symbol, typeArguments: Type[]): Type | undefined { const assignedType = getAssignedClassType(symbol); const valueType = getTypeOfSymbol(symbol); - const referenceType = valueType.symbol && !isInferredClassType(valueType) && getTypeReferenceTypeWorker(node, valueType.symbol, typeArguments); + const referenceType = valueType.symbol && valueType.symbol !== symbol && !isInferredClassType(valueType) && getTypeReferenceTypeWorker(node, valueType.symbol, typeArguments); if (referenceType || assignedType) { return referenceType && assignedType ? getIntersectionType([assignedType, referenceType]) : referenceType || assignedType; } - - // Resolve the type reference as a Type for the purpose of reporting errors. - resolveTypeReferenceName(getTypeReferenceName(node), SymbolFlags.Type); - return valueType; } function getTypeReferenceTypeWorker(node: NodeWithTypeArguments, symbol: Symbol, typeArguments: Type[]): Type | undefined { if (symbol.flags & (SymbolFlags.Class | SymbolFlags.Interface)) { + if (symbol.valueDeclaration && isBinaryExpression(symbol.valueDeclaration.parent)) { + const jsdocType = getJSDocTypeReference(node, symbol, typeArguments); + if (jsdocType) { + return jsdocType; + } + } return getTypeFromClassOrInterfaceReference(node, symbol, typeArguments); } @@ -20031,6 +20046,7 @@ namespace ts { getUnionType([removeDefinitelyFalsyTypes(leftType), rightType], UnionReduction.Subtype) : leftType; case SyntaxKind.EqualsToken: + checkSpecialAssignment(left, right); checkAssignmentOperator(rightType); return getRegularTypeOfObjectLiteral(rightType); case SyntaxKind.CommaToken: @@ -20040,6 +20056,24 @@ namespace ts { return rightType; } + function checkSpecialAssignment(left: Node, right: Expression) { + const special = getSpecialPropertyAssignmentKind(left.parent as BinaryExpression); + if (special === SpecialPropertyAssignmentKind.ModuleExports) { + const rightType = checkExpression(right, checkMode); + for (const prop of getPropertiesOfObjectType(rightType)) { + const propType = getTypeOfSymbol(prop); + if (propType.symbol && propType.symbol.flags & SymbolFlags.Class) { + const name = prop.escapedName; + const symbol = resolveName(prop.valueDeclaration, name, SymbolFlags.Type, undefined, name, /*isUse*/ false); + if (symbol) { + grammarErrorOnNode(symbol.declarations[0], Diagnostics.Duplicate_identifier_0, unescapeLeadingUnderscores(name)); + return grammarErrorOnNode(prop.valueDeclaration, Diagnostics.Duplicate_identifier_0, unescapeLeadingUnderscores(name)); + } + } + } + } + } + function isEvalNode(node: Expression) { return node.kind === SyntaxKind.Identifier && (node as Identifier).escapedText === "eval"; } diff --git a/src/harness/harness.ts b/src/harness/harness.ts index 9fe26ed51c3ba..c955c1acb59d2 100644 --- a/src/harness/harness.ts +++ b/src/harness/harness.ts @@ -217,7 +217,7 @@ namespace Utils { for (const childName in node) { if (childName === "parent" || childName === "nextContainer" || childName === "modifiers" || childName === "externalModuleIndicator" || // for now ignore jsdoc comments - childName === "jsDocComment" || childName === "checkJsDirective") { + childName === "jsDocComment" || childName === "checkJsDirective" || childName === "commonJsModuleIndicator") { continue; } const child = (node)[childName]; diff --git a/tests/baselines/reference/jsdocTypedefNoCrash.symbols b/tests/baselines/reference/jsdocTypedefNoCrash.symbols index 8724c9da8d171..51f5fdcc259fc 100644 --- a/tests/baselines/reference/jsdocTypedefNoCrash.symbols +++ b/tests/baselines/reference/jsdocTypedefNoCrash.symbols @@ -4,5 +4,5 @@ * }} */ export const foo = 5; ->foo : Symbol(foo, Decl(export.js, 4, 12)) +>foo : Symbol(foo, Decl(export.js, 4, 12), Decl(export.js, 1, 3)) diff --git a/tests/baselines/reference/typedefCrossModule.symbols b/tests/baselines/reference/typedefCrossModule.symbols new file mode 100644 index 0000000000000..2ba91903ba3ad --- /dev/null +++ b/tests/baselines/reference/typedefCrossModule.symbols @@ -0,0 +1,70 @@ +=== tests/cases/conformance/jsdoc/commonjs.d.ts === +declare var module: { exports: any}; +>module : Symbol(module, Decl(commonjs.d.ts, 0, 11)) +>exports : Symbol(exports, Decl(commonjs.d.ts, 0, 21)) + +=== tests/cases/conformance/jsdoc/mod1.js === +/// +/** @typedef {{ type: "a", x: 1 }} A */ +/** @typedef {{ type: "b", y: 1 }} B */ +/** @typedef {A | B} Both */ +module.exports = C +>module.exports : Symbol(exports, Decl(commonjs.d.ts, 0, 21)) +>module : Symbol(export=, Decl(mod1.js, 0, 0)) +>exports : Symbol(export=, Decl(mod1.js, 0, 0)) +>C : Symbol(C, Decl(mod1.js, 4, 18)) + +function C() { +>C : Symbol(C, Decl(mod1.js, 4, 18)) + + this.p = 1 +>p : Symbol(C.p, Decl(mod1.js, 5, 14)) +} + +=== tests/cases/conformance/jsdoc/mod2.js === +/// +/** @typedef {{ type: "a", x: 1 }} A */ +/** @typedef {{ type: "b", y: 1 }} B */ +/** @typedef {A | B} Both */ + +export function C() { +>C : Symbol(C, Decl(mod2.js, 0, 0)) + + this.p = 1 +>p : Symbol(C.p, Decl(mod2.js, 5, 21)) +} + +=== tests/cases/conformance/jsdoc/mod3.js === +/// +/** @typedef {{ type: "a", x: 1 }} A */ +/** @typedef {{ type: "b", y: 1 }} B */ +/** @typedef {A | B} Both */ + +exports.C = function() { +>exports.C : Symbol(C, Decl(mod3.js, 0, 0)) +>exports : Symbol(C, Decl(mod3.js, 0, 0)) +>C : Symbol(C, Decl(mod3.js, 0, 0)) + + this.p = 1 +>p : Symbol(C.p, Decl(mod3.js, 5, 24)) +} + +=== tests/cases/conformance/jsdoc/use.js === +/** @type {import('./mod1').Both} */ +var both1 = { type: 'a', x: 1 }; +>both1 : Symbol(both1, Decl(use.js, 1, 3)) +>type : Symbol(type, Decl(use.js, 1, 13)) +>x : Symbol(x, Decl(use.js, 1, 24)) + +/** @type {import('./mod2').Both} */ +var both2 = both1; +>both2 : Symbol(both2, Decl(use.js, 3, 3)) +>both1 : Symbol(both1, Decl(use.js, 1, 3)) + +/** @type {import('./mod3').Both} */ +var both3 = both2; +>both3 : Symbol(both3, Decl(use.js, 5, 3)) +>both2 : Symbol(both2, Decl(use.js, 3, 3)) + + + diff --git a/tests/baselines/reference/typedefCrossModule.types b/tests/baselines/reference/typedefCrossModule.types new file mode 100644 index 0000000000000..665fa4989918b --- /dev/null +++ b/tests/baselines/reference/typedefCrossModule.types @@ -0,0 +1,88 @@ +=== tests/cases/conformance/jsdoc/commonjs.d.ts === +declare var module: { exports: any}; +>module : { exports: any; } +>exports : any + +=== tests/cases/conformance/jsdoc/mod1.js === +/// +/** @typedef {{ type: "a", x: 1 }} A */ +/** @typedef {{ type: "b", y: 1 }} B */ +/** @typedef {A | B} Both */ +module.exports = C +>module.exports = C : typeof C +>module.exports : any +>module : { exports: any; } +>exports : any +>C : typeof C + +function C() { +>C : typeof C + + this.p = 1 +>this.p = 1 : 1 +>this.p : any +>this : any +>p : any +>1 : 1 +} + +=== tests/cases/conformance/jsdoc/mod2.js === +/// +/** @typedef {{ type: "a", x: 1 }} A */ +/** @typedef {{ type: "b", y: 1 }} B */ +/** @typedef {A | B} Both */ + +export function C() { +>C : typeof C + + this.p = 1 +>this.p = 1 : 1 +>this.p : any +>this : any +>p : any +>1 : 1 +} + +=== tests/cases/conformance/jsdoc/mod3.js === +/// +/** @typedef {{ type: "a", x: 1 }} A */ +/** @typedef {{ type: "b", y: 1 }} B */ +/** @typedef {A | B} Both */ + +exports.C = function() { +>exports.C = function() { this.p = 1} : typeof C +>exports.C : typeof C +>exports : typeof import("tests/cases/conformance/jsdoc/mod3") +>C : typeof C +>function() { this.p = 1} : typeof C + + this.p = 1 +>this.p = 1 : 1 +>this.p : any +>this : any +>p : any +>1 : 1 +} + +=== tests/cases/conformance/jsdoc/use.js === +/** @type {import('./mod1').Both} */ +var both1 = { type: 'a', x: 1 }; +>both1 : { type: "a"; x: 1; } | { type: "b"; y: 1; } +>{ type: 'a', x: 1 } : { type: "a"; x: 1; } +>type : "a" +>'a' : "a" +>x : 1 +>1 : 1 + +/** @type {import('./mod2').Both} */ +var both2 = both1; +>both2 : { type: "a"; x: 1; } | { type: "b"; y: 1; } +>both1 : { type: "a"; x: 1; } + +/** @type {import('./mod3').Both} */ +var both3 = both2; +>both3 : { type: "a"; x: 1; } | { type: "b"; y: 1; } +>both2 : { type: "a"; x: 1; } + + + diff --git a/tests/baselines/reference/typedefCrossModule2.errors.txt b/tests/baselines/reference/typedefCrossModule2.errors.txt new file mode 100644 index 0000000000000..04d28448e5795 --- /dev/null +++ b/tests/baselines/reference/typedefCrossModule2.errors.txt @@ -0,0 +1,64 @@ +tests/cases/conformance/jsdoc/mod1.js(3,23): error TS2300: Duplicate identifier 'Foo'. +tests/cases/conformance/jsdoc/mod1.js(4,7): error TS2300: Duplicate identifier 'Foo'. +tests/cases/conformance/jsdoc/mod1.js(6,23): error TS2300: Duplicate identifier 'Bar'. +tests/cases/conformance/jsdoc/mod1.js(7,9): error TS2300: Duplicate identifier 'Bar'. +tests/cases/conformance/jsdoc/mod1.js(9,5): error TS2300: Duplicate identifier 'Baz'. +tests/cases/conformance/jsdoc/mod1.js(10,1): error TS2304: Cannot find name 'module'. +tests/cases/conformance/jsdoc/mod1.js(11,5): error TS2300: Duplicate identifier 'Baz'. +tests/cases/conformance/jsdoc/mod1.js(23,1): error TS2304: Cannot find name 'module'. +tests/cases/conformance/jsdoc/use.js(1,11): error TS2304: Cannot find name 'require'. + + +==== tests/cases/conformance/jsdoc/use.js (1 errors) ==== + var mod = require('./mod1.js'); + ~~~~~~~ +!!! error TS2304: Cannot find name 'require'. + /** @type {import("./mod1.js").Baz} */ + var b; + /** @type {mod.Baz} */ + var bb; + var bbb = new mod.Baz(); + +==== tests/cases/conformance/jsdoc/mod1.js (8 errors) ==== + // error + + /** @typedef {number} Foo */ + ~~~ +!!! error TS2300: Duplicate identifier 'Foo'. + class Foo { } // should error + ~~~ +!!! error TS2300: Duplicate identifier 'Foo'. + + /** @typedef {number} Bar */ + ~~~ +!!! error TS2300: Duplicate identifier 'Bar'. + exports.Bar = class { } + ~~~ +!!! error TS2300: Duplicate identifier 'Bar'. + + /** @typedef {number} Baz */ + ~~~~~~~~~~~~~~~~~~~~~ +!!! error TS2300: Duplicate identifier 'Baz'. + module.exports = { + ~~~~~~ +!!! error TS2304: Cannot find name 'module'. + Baz: class { } + ~~~~~~~~~~~~~~ +!!! error TS2300: Duplicate identifier 'Baz'. + } + + // ok + + /** @typedef {number} Qux */ + var Qux = 2; + + /** @typedef {number} Quid */ + exports.Quid = 2; + + /** @typedef {number} Quack */ + module.exports = { + ~~~~~~ +!!! error TS2304: Cannot find name 'module'. + Quack: 2 + } + \ No newline at end of file diff --git a/tests/baselines/reference/typedefCrossModule2.symbols b/tests/baselines/reference/typedefCrossModule2.symbols new file mode 100644 index 0000000000000..01afe2b9bdb75 --- /dev/null +++ b/tests/baselines/reference/typedefCrossModule2.symbols @@ -0,0 +1,60 @@ +=== tests/cases/conformance/jsdoc/use.js === +var mod = require('./mod1.js'); +>mod : Symbol(mod, Decl(use.js, 0, 3)) +>'./mod1.js' : Symbol("tests/cases/conformance/jsdoc/mod1", Decl(mod1.js, 0, 0)) + +/** @type {import("./mod1.js").Baz} */ +var b; +>b : Symbol(b, Decl(use.js, 2, 3)) + +/** @type {mod.Baz} */ +var bb; +>bb : Symbol(bb, Decl(use.js, 4, 3)) + +var bbb = new mod.Baz(); +>bbb : Symbol(bbb, Decl(use.js, 5, 3)) +>mod : Symbol(mod, Decl(use.js, 0, 3)) + +=== tests/cases/conformance/jsdoc/mod1.js === +// error + +/** @typedef {number} Foo */ +class Foo { } // should error +>Foo : Symbol(Foo, Decl(mod1.js, 0, 0)) + +/** @typedef {number} Bar */ +exports.Bar = class { } +>exports.Bar : Symbol(Bar, Decl(mod1.js, 3, 13)) +>exports : Symbol(Bar, Decl(mod1.js, 3, 13)) +>Bar : Symbol(Bar, Decl(mod1.js, 3, 13)) + +/** @typedef {number} Baz */ +module.exports = { +>module : Symbol(export=, Decl(mod1.js, 6, 23), Decl(mod1.js, 19, 17)) +>exports : Symbol(export=, Decl(mod1.js, 6, 23), Decl(mod1.js, 19, 17)) + + Baz: class { } +>Baz : Symbol(Baz, Decl(mod1.js, 9, 18)) +} + +// ok + +/** @typedef {number} Qux */ +var Qux = 2; +>Qux : Symbol(Qux, Decl(mod1.js, 16, 3), Decl(mod1.js, 15, 4)) + +/** @typedef {number} Quid */ +exports.Quid = 2; +>exports.Quid : Symbol(Quid, Decl(mod1.js, 16, 12), Decl(mod1.js, 18, 4)) +>exports : Symbol(Quid, Decl(mod1.js, 16, 12), Decl(mod1.js, 18, 4)) +>Quid : Symbol(Quid, Decl(mod1.js, 16, 12), Decl(mod1.js, 18, 4)) + +/** @typedef {number} Quack */ +module.exports = { +>module : Symbol(export=, Decl(mod1.js, 6, 23), Decl(mod1.js, 19, 17)) +>exports : Symbol(export=, Decl(mod1.js, 6, 23), Decl(mod1.js, 19, 17)) + + Quack: 2 +>Quack : Symbol(Quack, Decl(mod1.js, 22, 18)) +} + diff --git a/tests/baselines/reference/typedefCrossModule2.types b/tests/baselines/reference/typedefCrossModule2.types new file mode 100644 index 0000000000000..67e4b7a4e890e --- /dev/null +++ b/tests/baselines/reference/typedefCrossModule2.types @@ -0,0 +1,78 @@ +=== tests/cases/conformance/jsdoc/use.js === +var mod = require('./mod1.js'); +>mod : { [x: string]: any; Baz: any; Bar: typeof Bar; Quid: any; } | { [x: string]: any; Quack: any; Bar: typeof Bar; Quid: any; } +>require('./mod1.js') : { [x: string]: any; Baz: any; Bar: typeof Bar; Quid: any; } | { [x: string]: any; Quack: any; Bar: typeof Bar; Quid: any; } +>require : any +>'./mod1.js' : "./mod1.js" + +/** @type {import("./mod1.js").Baz} */ +var b; +>b : number + +/** @type {mod.Baz} */ +var bb; +>bb : number + +var bbb = new mod.Baz(); +>bbb : any +>new mod.Baz() : any +>mod.Baz : any +>mod : { [x: string]: any; Baz: any; Bar: typeof Bar; Quid: any; } | { [x: string]: any; Quack: any; Bar: typeof Bar; Quid: any; } +>Baz : any + +=== tests/cases/conformance/jsdoc/mod1.js === +// error + +/** @typedef {number} Foo */ +class Foo { } // should error +>Foo : Foo + +/** @typedef {number} Bar */ +exports.Bar = class { } +>exports.Bar = class { } : typeof Bar +>exports.Bar : typeof Bar +>exports : typeof import("tests/cases/conformance/jsdoc/mod1") +>Bar : typeof Bar +>class { } : typeof Bar + +/** @typedef {number} Baz */ +module.exports = { +>module.exports = { Baz: class { }} : { [x: string]: any; Baz: typeof Baz; } +>module.exports : any +>module : any +>exports : any +>{ Baz: class { }} : { [x: string]: any; Baz: typeof Baz; } + + Baz: class { } +>Baz : typeof Baz +>class { } : typeof Baz +} + +// ok + +/** @typedef {number} Qux */ +var Qux = 2; +>Qux : number +>2 : 2 + +/** @typedef {number} Quid */ +exports.Quid = 2; +>exports.Quid = 2 : 2 +>exports.Quid : any +>exports : typeof import("tests/cases/conformance/jsdoc/mod1") +>Quid : any +>2 : 2 + +/** @typedef {number} Quack */ +module.exports = { +>module.exports = { Quack: 2} : { [x: string]: any; Quack: number; } +>module.exports : any +>module : any +>exports : any +>{ Quack: 2} : { [x: string]: any; Quack: number; } + + Quack: 2 +>Quack : number +>2 : 2 +} + diff --git a/tests/baselines/reference/typedefCrossModule3.errors.txt b/tests/baselines/reference/typedefCrossModule3.errors.txt new file mode 100644 index 0000000000000..5309528fb4e6c --- /dev/null +++ b/tests/baselines/reference/typedefCrossModule3.errors.txt @@ -0,0 +1,18 @@ +tests/cases/conformance/jsdoc/mod2.js(1,5): error TS2300: Duplicate identifier 'Foo'. +tests/cases/conformance/jsdoc/mod2.js(3,1): error TS2300: Duplicate identifier 'Foo'. +tests/cases/conformance/jsdoc/mod2.js(4,1): error TS2304: Cannot find name 'module'. + + +==== tests/cases/conformance/jsdoc/mod2.js (3 errors) ==== + /** @typedef {number} Foo */ + ~~~~~~~~~~~~~~~~~~~~~ +!!! error TS2300: Duplicate identifier 'Foo'. + const ns = {}; + ns.Foo = class {} + ~~~~~~ +!!! error TS2300: Duplicate identifier 'Foo'. + module.exports = ns; + ~~~~~~ +!!! error TS2304: Cannot find name 'module'. + + \ No newline at end of file diff --git a/tests/baselines/reference/typedefCrossModule3.symbols b/tests/baselines/reference/typedefCrossModule3.symbols new file mode 100644 index 0000000000000..3e1c8a55535df --- /dev/null +++ b/tests/baselines/reference/typedefCrossModule3.symbols @@ -0,0 +1,16 @@ +=== tests/cases/conformance/jsdoc/mod2.js === +/** @typedef {number} Foo */ +const ns = {}; +>ns : Symbol(ns, Decl(mod2.js, 1, 5), Decl(mod2.js, 1, 14)) + +ns.Foo = class {} +>ns.Foo : Symbol(Foo, Decl(mod2.js, 1, 14)) +>ns : Symbol(ns, Decl(mod2.js, 1, 5), Decl(mod2.js, 1, 14)) +>Foo : Symbol(Foo, Decl(mod2.js, 1, 14)) + +module.exports = ns; +>module : Symbol(export=, Decl(mod2.js, 2, 17)) +>exports : Symbol(export=, Decl(mod2.js, 2, 17)) +>ns : Symbol(ns, Decl(mod2.js, 1, 5), Decl(mod2.js, 1, 14)) + + diff --git a/tests/baselines/reference/typedefCrossModule3.types b/tests/baselines/reference/typedefCrossModule3.types new file mode 100644 index 0000000000000..9d3767dcbbaba --- /dev/null +++ b/tests/baselines/reference/typedefCrossModule3.types @@ -0,0 +1,21 @@ +=== tests/cases/conformance/jsdoc/mod2.js === +/** @typedef {number} Foo */ +const ns = {}; +>ns : { [x: string]: any; Foo: typeof Foo; } +>{} : { [x: string]: any; Foo: typeof Foo; } + +ns.Foo = class {} +>ns.Foo = class {} : typeof Foo +>ns.Foo : typeof Foo +>ns : { [x: string]: any; Foo: typeof Foo; } +>Foo : typeof Foo +>class {} : typeof Foo + +module.exports = ns; +>module.exports = ns : { [x: string]: any; Foo: typeof Foo; } +>module.exports : any +>module : any +>exports : any +>ns : { [x: string]: any; Foo: typeof Foo; } + + diff --git a/tests/baselines/reference/typedefCrossModule4.errors.txt b/tests/baselines/reference/typedefCrossModule4.errors.txt new file mode 100644 index 0000000000000..5dcd9ac017bfe --- /dev/null +++ b/tests/baselines/reference/typedefCrossModule4.errors.txt @@ -0,0 +1,17 @@ +tests/cases/conformance/jsdoc/mod3.js(1,5): error TS2300: Duplicate identifier 'Foo'. +tests/cases/conformance/jsdoc/mod3.js(3,1): error TS2304: Cannot find name 'module'. +tests/cases/conformance/jsdoc/mod3.js(3,20): error TS2300: Duplicate identifier 'Foo'. + + +==== tests/cases/conformance/jsdoc/mod3.js (3 errors) ==== + /** @typedef {number} Foo */ + ~~~~~~~~~~~~~~~~~~~~~ +!!! error TS2300: Duplicate identifier 'Foo'. + class Bar { } + module.exports = { Foo: Bar }; + ~~~~~~ +!!! error TS2304: Cannot find name 'module'. + ~~~~~~~~ +!!! error TS2300: Duplicate identifier 'Foo'. + + \ No newline at end of file diff --git a/tests/baselines/reference/typedefCrossModule4.symbols b/tests/baselines/reference/typedefCrossModule4.symbols new file mode 100644 index 0000000000000..aa478df301a9c --- /dev/null +++ b/tests/baselines/reference/typedefCrossModule4.symbols @@ -0,0 +1,12 @@ +=== tests/cases/conformance/jsdoc/mod3.js === +/** @typedef {number} Foo */ +class Bar { } +>Bar : Symbol(Bar, Decl(mod3.js, 0, 0)) + +module.exports = { Foo: Bar }; +>module : Symbol(export=, Decl(mod3.js, 1, 13)) +>exports : Symbol(export=, Decl(mod3.js, 1, 13)) +>Foo : Symbol(Foo, Decl(mod3.js, 2, 18)) +>Bar : Symbol(Bar, Decl(mod3.js, 0, 0)) + + diff --git a/tests/baselines/reference/typedefCrossModule4.types b/tests/baselines/reference/typedefCrossModule4.types new file mode 100644 index 0000000000000..a0d14942ef3b5 --- /dev/null +++ b/tests/baselines/reference/typedefCrossModule4.types @@ -0,0 +1,15 @@ +=== tests/cases/conformance/jsdoc/mod3.js === +/** @typedef {number} Foo */ +class Bar { } +>Bar : Bar + +module.exports = { Foo: Bar }; +>module.exports = { Foo: Bar } : { [x: string]: any; Foo: typeof Bar; } +>module.exports : any +>module : any +>exports : any +>{ Foo: Bar } : { [x: string]: any; Foo: typeof Bar; } +>Foo : typeof Bar +>Bar : typeof Bar + + diff --git a/tests/cases/conformance/jsdoc/typedefCrossModule.ts b/tests/cases/conformance/jsdoc/typedefCrossModule.ts new file mode 100644 index 0000000000000..772a41da7bf82 --- /dev/null +++ b/tests/cases/conformance/jsdoc/typedefCrossModule.ts @@ -0,0 +1,44 @@ +// @noEmit: true +// @allowJs: true +// @checkJs: true +// @Filename: commonjs.d.ts +declare var module: { exports: any}; +// @Filename: mod1.js +/// +/** @typedef {{ type: "a", x: 1 }} A */ +/** @typedef {{ type: "b", y: 1 }} B */ +/** @typedef {A | B} Both */ +module.exports = C +function C() { + this.p = 1 +} + +// @Filename: mod2.js +/// +/** @typedef {{ type: "a", x: 1 }} A */ +/** @typedef {{ type: "b", y: 1 }} B */ +/** @typedef {A | B} Both */ + +export function C() { + this.p = 1 +} + +// @Filename: mod3.js +/// +/** @typedef {{ type: "a", x: 1 }} A */ +/** @typedef {{ type: "b", y: 1 }} B */ +/** @typedef {A | B} Both */ + +exports.C = function() { + this.p = 1 +} + +// @Filename: use.js +/** @type {import('./mod1').Both} */ +var both1 = { type: 'a', x: 1 }; +/** @type {import('./mod2').Both} */ +var both2 = both1; +/** @type {import('./mod3').Both} */ +var both3 = both2; + + diff --git a/tests/cases/conformance/jsdoc/typedefCrossModule2.ts b/tests/cases/conformance/jsdoc/typedefCrossModule2.ts new file mode 100644 index 0000000000000..6bb00957ef396 --- /dev/null +++ b/tests/cases/conformance/jsdoc/typedefCrossModule2.ts @@ -0,0 +1,39 @@ +// @noEmit: true +// @allowJs: true +// @checkJs: true +// @Filename: mod1.js + +// error + +/** @typedef {number} Foo */ +class Foo { } // should error + +/** @typedef {number} Bar */ +exports.Bar = class { } + +/** @typedef {number} Baz */ +module.exports = { + Baz: class { } +} + +// ok + +/** @typedef {number} Qux */ +var Qux = 2; + +/** @typedef {number} Quid */ +exports.Quid = 2; + +/** @typedef {number} Quack */ +module.exports = { + Quack: 2 +} + +// @Filename: use.js + +var mod = require('./mod1.js'); +/** @type {import("./mod1.js").Baz} */ +var b; +/** @type {mod.Baz} */ +var bb; +var bbb = new mod.Baz(); diff --git a/tests/cases/conformance/jsdoc/typedefCrossModule3.ts b/tests/cases/conformance/jsdoc/typedefCrossModule3.ts new file mode 100644 index 0000000000000..d6005b541dcb7 --- /dev/null +++ b/tests/cases/conformance/jsdoc/typedefCrossModule3.ts @@ -0,0 +1,10 @@ +// @noEmit: true +// @allowJs: true +// @checkJs: true +// @Filename: mod2.js + +/** @typedef {number} Foo */ +const ns = {}; +ns.Foo = class {} +module.exports = ns; + diff --git a/tests/cases/conformance/jsdoc/typedefCrossModule4.ts b/tests/cases/conformance/jsdoc/typedefCrossModule4.ts new file mode 100644 index 0000000000000..09d27246c7c8f --- /dev/null +++ b/tests/cases/conformance/jsdoc/typedefCrossModule4.ts @@ -0,0 +1,9 @@ +// @noEmit: true +// @allowJs: true +// @checkJs: true +// @Filename: mod3.js + +/** @typedef {number} Foo */ +class Bar { } +module.exports = { Foo: Bar }; + diff --git a/tests/cases/fourslash/findAllRefsClassExpression2.ts b/tests/cases/fourslash/findAllRefsClassExpression2.ts index f25e64a2c68e8..1214e3f7c8856 100644 --- a/tests/cases/fourslash/findAllRefsClassExpression2.ts +++ b/tests/cases/fourslash/findAllRefsClassExpression2.ts @@ -10,7 +10,7 @@ ////[|A|]; const [r0, r1, r2] = test.ranges(); -const defs = { definition: "(property) A: typeof A", ranges: [r0] }; -const imports = { definition: "(alias) (property) A: typeof A\nimport A", ranges: [r1, r2] }; +const defs = { definition: "class A\n(property) A: typeof A", ranges: [r0] }; +const imports = { definition: "(alias) class A\n(alias) (property) A: typeof A\nimport A", ranges: [r1, r2] }; verify.referenceGroups([r0], [defs, imports]); verify.referenceGroups([r1, r2], [imports, defs]); diff --git a/tests/cases/fourslash/refactorConvertToEs6Module_export_named.ts b/tests/cases/fourslash/refactorConvertToEs6Module_export_named.ts index e7ee290858038..f8eba2b02134b 100644 --- a/tests/cases/fourslash/refactorConvertToEs6Module_export_named.ts +++ b/tests/cases/fourslash/refactorConvertToEs6Module_export_named.ts @@ -21,7 +21,9 @@ verify.codeFix({ description: "Convert to ES6 module", newFileContent: `export function f() {} -export class C {} +const _C = class { +}; +export { _C as C }; export const x = 0; export function a1() {} export function a2() { return 0; } diff --git a/tests/cases/fourslash/refactorConvertToEs6Module_export_namedClassExpression.ts b/tests/cases/fourslash/refactorConvertToEs6Module_export_namedClassExpression.ts index e1cb86e8d682e..47b8a5e0b32cf 100644 --- a/tests/cases/fourslash/refactorConvertToEs6Module_export_namedClassExpression.ts +++ b/tests/cases/fourslash/refactorConvertToEs6Module_export_namedClassExpression.ts @@ -10,6 +10,12 @@ verify.codeFix({ description: "Convert to ES6 module", newFileContent: -`export const C = class E { static instance = new E(); } -export class D { static instance = new D(); }`, +`const _C = class E { + static instance = new E(); +}; +export { _C as C }; +const _D = class D { + static instance = new D(); +}; +export { _D as D };`, }); diff --git a/tests/cases/fourslash/refactorConvertToEs6Module_expressionToDeclaration.ts b/tests/cases/fourslash/refactorConvertToEs6Module_expressionToDeclaration.ts index 47ff26f459ed2..b360334767f9e 100644 --- a/tests/cases/fourslash/refactorConvertToEs6Module_expressionToDeclaration.ts +++ b/tests/cases/fourslash/refactorConvertToEs6Module_expressionToDeclaration.ts @@ -11,5 +11,8 @@ verify.codeFix({ description: "Convert to ES6 module", newFileContent: `export async function* f(p) { p; } -export class C extends D { m() {} }`, +const _C = class C extends D { + m() { } +}; +export { _C as C };`, });