Always export typedefs (#23723)

* Always export typedefs

This actually just required deleting a check in declareModuleMembers
and checking for external AND commonjs modules in a couple of places.

However, while experimenting with this feature, I discovered that even
previously-exported typedefs would only be exported if they came after a
commonjs export node. So I added a commonjs check to the pass in the
parser. It will not catch nested module.exports, but it will catch
top-level assignments.

The new test tests both changes.

* Post-bind typedef instead of pre-checking for commonjs

* Duplicate identifier errors

* Fix class type reference resolution+update baselines

* Move to a type-based check for duplicate identifiers
This commit is contained in:
Nathan Shively-Sanders
2018-04-30 14:55:26 -07:00
committed by GitHub
parent 0bbf4d5c48
commit 7cda045d52
23 changed files with 651 additions and 22 deletions

View File

@@ -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(<SourceFile>container)) {
if (isExternalOrCommonJsModule(<SourceFile>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(<Declaration>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);
}
}

View File

@@ -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 assignedType = getAssignedClassType(symbol);
const valueType = getTypeOfSymbol(symbol);
const referenceType = valueType.symbol && !isInferredClassType(valueType) && getTypeReferenceTypeWorker(node, valueType.symbol, typeArguments);
if (referenceType || assignedType) {
return referenceType && assignedType ? getIntersectionType([assignedType, referenceType]) : referenceType || assignedType;
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 valueType;
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 && valueType.symbol !== symbol && !isInferredClassType(valueType) && getTypeReferenceTypeWorker(node, valueType.symbol, typeArguments);
if (referenceType || assignedType) {
return referenceType && assignedType ? getIntersectionType([assignedType, referenceType]) : referenceType || assignedType;
}
}
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";
}

View File

@@ -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 = (<any>node)[childName];

View File

@@ -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))

View File

@@ -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 ===
/// <reference path="./commonjs.d.ts"/>
/** @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 ===
/// <reference path="./commonjs.d.ts"/>
/** @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 ===
/// <reference path="./commonjs.d.ts"/>
/** @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))

View File

@@ -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 ===
/// <reference path="./commonjs.d.ts"/>
/** @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 ===
/// <reference path="./commonjs.d.ts"/>
/** @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 ===
/// <reference path="./commonjs.d.ts"/>
/** @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; }

View File

@@ -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
}

View File

@@ -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))
}

View File

@@ -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
}

View File

@@ -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'.

View File

@@ -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))

View File

@@ -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; }

View File

@@ -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'.

View File

@@ -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))

View File

@@ -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

View File

@@ -0,0 +1,44 @@
// @noEmit: true
// @allowJs: true
// @checkJs: true
// @Filename: commonjs.d.ts
declare var module: { exports: any};
// @Filename: mod1.js
/// <reference path="./commonjs.d.ts"/>
/** @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
/// <reference path="./commonjs.d.ts"/>
/** @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
/// <reference path="./commonjs.d.ts"/>
/** @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;

View File

@@ -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();

View File

@@ -0,0 +1,10 @@
// @noEmit: true
// @allowJs: true
// @checkJs: true
// @Filename: mod2.js
/** @typedef {number} Foo */
const ns = {};
ns.Foo = class {}
module.exports = ns;

View File

@@ -0,0 +1,9 @@
// @noEmit: true
// @allowJs: true
// @checkJs: true
// @Filename: mod3.js
/** @typedef {number} Foo */
class Bar { }
module.exports = { Foo: Bar };

View File

@@ -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]);

View File

@@ -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; }

View File

@@ -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 };`,
});

View File

@@ -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 };`,
});