Skip to content

Commit 9498978

Browse files
authored
In JS declaration emit, move imports painted in nested contexts to the root private context (microsoft#39818)
* In JS declaration emit, move imports painted in nested contexts to the root private context * Add test for nathan
1 parent 1b97d03 commit 9498978

10 files changed

+665
-7
lines changed

src/compiler/checker.ts

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5899,7 +5899,7 @@ namespace ts {
58995899
const enclosingDeclaration = context.enclosingDeclaration!;
59005900
let results: Statement[] = [];
59015901
const visitedSymbols = new Set<number>();
5902-
let deferredPrivates: ESMap<SymbolId, Symbol> | undefined;
5902+
const deferredPrivatesStack: ESMap<SymbolId, Symbol>[] = [];
59035903
const oldcontext = context;
59045904
context = {
59055905
...oldcontext,
@@ -6110,9 +6110,8 @@ namespace ts {
61106110
}
61116111

61126112
function visitSymbolTable(symbolTable: SymbolTable, suppressNewPrivateContext?: boolean, propertyAsAlias?: boolean) {
6113-
const oldDeferredPrivates = deferredPrivates;
61146113
if (!suppressNewPrivateContext) {
6115-
deferredPrivates = new Map();
6114+
deferredPrivatesStack.push(new Map());
61166115
}
61176116
symbolTable.forEach((symbol: Symbol) => {
61186117
serializeSymbol(symbol, /*isPrivate*/ false, !!propertyAsAlias);
@@ -6121,11 +6120,11 @@ namespace ts {
61216120
// deferredPrivates will be filled up by visiting the symbol table
61226121
// And will continue to iterate as elements are added while visited `deferredPrivates`
61236122
// (As that's how a map iterator is defined to work)
6124-
deferredPrivates!.forEach((symbol: Symbol) => {
6123+
deferredPrivatesStack[deferredPrivatesStack.length - 1].forEach((symbol: Symbol) => {
61256124
serializeSymbol(symbol, /*isPrivate*/ true, !!propertyAsAlias);
61266125
});
6126+
deferredPrivatesStack.pop();
61276127
}
6128-
deferredPrivates = oldDeferredPrivates;
61296128
}
61306129

61316130
function serializeSymbol(symbol: Symbol, isPrivate: boolean, propertyAsAlias: boolean) {
@@ -6309,9 +6308,19 @@ namespace ts {
63096308

63106309
function includePrivateSymbol(symbol: Symbol) {
63116310
if (some(symbol.declarations, isParameterDeclaration)) return;
6312-
Debug.assertIsDefined(deferredPrivates);
6311+
Debug.assertIsDefined(deferredPrivatesStack[deferredPrivatesStack.length - 1]);
63136312
getUnusedName(unescapeLeadingUnderscores(symbol.escapedName), symbol); // Call to cache unique name for symbol
6314-
deferredPrivates.set(getSymbolId(symbol), symbol);
6313+
// Blanket moving (import) aliases into the root private context should work, since imports are not valid within namespaces
6314+
// (so they must have been in the root to begin with if they were real imports) cjs `require` aliases (an upcoming feature)
6315+
// will throw a wrench in this, since those may have been nested, but we'll need to synthesize them in the outer scope
6316+
// anyway, as that's the only place the import they translate to is valid. In such a case, we might need to use a unique name
6317+
// for the moved import; which hopefully the above `getUnusedName` call should produce.
6318+
const isExternalImportAlias = !!(symbol.flags & SymbolFlags.Alias) && !some(symbol.declarations, d =>
6319+
!!findAncestor(d, isExportDeclaration) ||
6320+
isNamespaceExport(d) ||
6321+
(isImportEqualsDeclaration(d) && !isExternalModuleReference(d.moduleReference))
6322+
);
6323+
deferredPrivatesStack[isExternalImportAlias ? 0 : (deferredPrivatesStack.length - 1)].set(getSymbolId(symbol), symbol);
63156324
}
63166325

63176326
function isExportingScope(enclosingDeclaration: Node) {
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
//// [tests/cases/conformance/jsdoc/declarations/jsDeclarationsImportAliasExposedWithinNamespace.ts] ////
2+
3+
//// [file.js]
4+
/**
5+
* @namespace myTypes
6+
* @global
7+
* @type {Object<string,*>}
8+
*/
9+
const myTypes = {
10+
// SOME PROPS HERE
11+
};
12+
13+
/** @typedef {string|RegExp|Array<string|RegExp>} myTypes.typeA */
14+
15+
/**
16+
* @typedef myTypes.typeB
17+
* @property {myTypes.typeA} prop1 - Prop 1.
18+
* @property {string} prop2 - Prop 2.
19+
*/
20+
21+
/** @typedef {myTypes.typeB|Function} myTypes.typeC */
22+
23+
export {myTypes};
24+
//// [file2.js]
25+
import {myTypes} from './file.js';
26+
27+
/**
28+
* @namespace testFnTypes
29+
* @global
30+
* @type {Object<string,*>}
31+
*/
32+
const testFnTypes = {
33+
// SOME PROPS HERE
34+
};
35+
36+
/** @typedef {boolean|myTypes.typeC} testFnTypes.input */
37+
38+
/**
39+
* @function testFn
40+
* @description A test function.
41+
* @param {testFnTypes.input} input - Input.
42+
* @returns {number|null} Result.
43+
*/
44+
function testFn(input) {
45+
if (typeof input === 'number') {
46+
return 2 * input;
47+
} else {
48+
return null;
49+
}
50+
}
51+
52+
export {testFn, testFnTypes};
53+
54+
55+
56+
//// [file.d.ts]
57+
/**
58+
* @namespace myTypes
59+
* @global
60+
* @type {Object<string,*>}
61+
*/
62+
export const myTypes: {
63+
[x: string]: any;
64+
};
65+
export namespace myTypes {
66+
type typeA = string | RegExp | (string | RegExp)[];
67+
type typeB = {
68+
/**
69+
* - Prop 1.
70+
*/
71+
prop1: string | RegExp | (string | RegExp)[];
72+
/**
73+
* - Prop 2.
74+
*/
75+
prop2: string;
76+
};
77+
type typeC = Function | typeB;
78+
}
79+
//// [file2.d.ts]
80+
/** @typedef {boolean|myTypes.typeC} testFnTypes.input */
81+
/**
82+
* @function testFn
83+
* @description A test function.
84+
* @param {testFnTypes.input} input - Input.
85+
* @returns {number|null} Result.
86+
*/
87+
export function testFn(input: boolean | Function | myTypes.typeB): number | null;
88+
/**
89+
* @namespace testFnTypes
90+
* @global
91+
* @type {Object<string,*>}
92+
*/
93+
export const testFnTypes: {
94+
[x: string]: any;
95+
};
96+
export namespace testFnTypes {
97+
type input = boolean | Function | myTypes.typeB;
98+
}
99+
import { myTypes } from "./file.js";
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
=== tests/cases/conformance/jsdoc/declarations/file.js ===
2+
/**
3+
* @namespace myTypes
4+
* @global
5+
* @type {Object<string,*>}
6+
*/
7+
const myTypes = {
8+
>myTypes : Symbol(myTypes, Decl(file.js, 5, 5), Decl(file.js, 9, 50), Decl(file.js, 12, 12), Decl(file.js, 17, 38))
9+
10+
// SOME PROPS HERE
11+
};
12+
13+
/** @typedef {string|RegExp|Array<string|RegExp>} myTypes.typeA */
14+
15+
/**
16+
* @typedef myTypes.typeB
17+
* @property {myTypes.typeA} prop1 - Prop 1.
18+
* @property {string} prop2 - Prop 2.
19+
*/
20+
21+
/** @typedef {myTypes.typeB|Function} myTypes.typeC */
22+
23+
export {myTypes};
24+
>myTypes : Symbol(myTypes, Decl(file.js, 19, 8))
25+
26+
=== tests/cases/conformance/jsdoc/declarations/file2.js ===
27+
import {myTypes} from './file.js';
28+
>myTypes : Symbol(myTypes, Decl(file2.js, 0, 8))
29+
30+
/**
31+
* @namespace testFnTypes
32+
* @global
33+
* @type {Object<string,*>}
34+
*/
35+
const testFnTypes = {
36+
>testFnTypes : Symbol(testFnTypes, Decl(file2.js, 7, 5), Decl(file2.js, 11, 37))
37+
38+
// SOME PROPS HERE
39+
};
40+
41+
/** @typedef {boolean|myTypes.typeC} testFnTypes.input */
42+
43+
/**
44+
* @function testFn
45+
* @description A test function.
46+
* @param {testFnTypes.input} input - Input.
47+
* @returns {number|null} Result.
48+
*/
49+
function testFn(input) {
50+
>testFn : Symbol(testFn, Decl(file2.js, 9, 2))
51+
>input : Symbol(input, Decl(file2.js, 19, 16))
52+
53+
if (typeof input === 'number') {
54+
>input : Symbol(input, Decl(file2.js, 19, 16))
55+
56+
return 2 * input;
57+
>input : Symbol(input, Decl(file2.js, 19, 16))
58+
59+
} else {
60+
return null;
61+
}
62+
}
63+
64+
export {testFn, testFnTypes};
65+
>testFn : Symbol(testFn, Decl(file2.js, 27, 8))
66+
>testFnTypes : Symbol(testFnTypes, Decl(file2.js, 27, 15))
67+
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
=== tests/cases/conformance/jsdoc/declarations/file.js ===
2+
/**
3+
* @namespace myTypes
4+
* @global
5+
* @type {Object<string,*>}
6+
*/
7+
const myTypes = {
8+
>myTypes : { [x: string]: any; }
9+
>{ // SOME PROPS HERE} : {}
10+
11+
// SOME PROPS HERE
12+
};
13+
14+
/** @typedef {string|RegExp|Array<string|RegExp>} myTypes.typeA */
15+
16+
/**
17+
* @typedef myTypes.typeB
18+
* @property {myTypes.typeA} prop1 - Prop 1.
19+
* @property {string} prop2 - Prop 2.
20+
*/
21+
22+
/** @typedef {myTypes.typeB|Function} myTypes.typeC */
23+
24+
export {myTypes};
25+
>myTypes : { [x: string]: any; }
26+
27+
=== tests/cases/conformance/jsdoc/declarations/file2.js ===
28+
import {myTypes} from './file.js';
29+
>myTypes : { [x: string]: any; }
30+
31+
/**
32+
* @namespace testFnTypes
33+
* @global
34+
* @type {Object<string,*>}
35+
*/
36+
const testFnTypes = {
37+
>testFnTypes : { [x: string]: any; }
38+
>{ // SOME PROPS HERE} : {}
39+
40+
// SOME PROPS HERE
41+
};
42+
43+
/** @typedef {boolean|myTypes.typeC} testFnTypes.input */
44+
45+
/**
46+
* @function testFn
47+
* @description A test function.
48+
* @param {testFnTypes.input} input - Input.
49+
* @returns {number|null} Result.
50+
*/
51+
function testFn(input) {
52+
>testFn : (input: testFnTypes.input) => number | null
53+
>input : boolean | Function | myTypes.typeB
54+
55+
if (typeof input === 'number') {
56+
>typeof input === 'number' : boolean
57+
>typeof input : "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function"
58+
>input : boolean | Function | myTypes.typeB
59+
>'number' : "number"
60+
61+
return 2 * input;
62+
>2 * input : number
63+
>2 : 2
64+
>input : never
65+
66+
} else {
67+
return null;
68+
>null : null
69+
}
70+
}
71+
72+
export {testFn, testFnTypes};
73+
>testFn : (input: boolean | Function | myTypes.typeB) => number
74+
>testFnTypes : { [x: string]: any; }
75+
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
tests/cases/conformance/jsdoc/declarations/file2.js(12,31): error TS2694: Namespace 'myTypes' has no exported member 'typeC'.
2+
3+
4+
==== tests/cases/conformance/jsdoc/declarations/file2.js (1 errors) ====
5+
const {myTypes} = require('./file.js');
6+
7+
/**
8+
* @namespace testFnTypes
9+
* @global
10+
* @type {Object<string,*>}
11+
*/
12+
const testFnTypes = {
13+
// SOME PROPS HERE
14+
};
15+
16+
/** @typedef {boolean|myTypes.typeC} testFnTypes.input */
17+
~~~~~
18+
!!! error TS2694: Namespace 'myTypes' has no exported member 'typeC'.
19+
20+
/**
21+
* @function testFn
22+
* @description A test function.
23+
* @param {testFnTypes.input} input - Input.
24+
* @returns {number|null} Result.
25+
*/
26+
function testFn(input) {
27+
if (typeof input === 'number') {
28+
return 2 * input;
29+
} else {
30+
return null;
31+
}
32+
}
33+
34+
module.exports = {testFn, testFnTypes};
35+
==== tests/cases/conformance/jsdoc/declarations/file.js (0 errors) ====
36+
/**
37+
* @namespace myTypes
38+
* @global
39+
* @type {Object<string,*>}
40+
*/
41+
const myTypes = {
42+
// SOME PROPS HERE
43+
};
44+
45+
/** @typedef {string|RegExp|Array<string|RegExp>} myTypes.typeA */
46+
47+
/**
48+
* @typedef myTypes.typeB
49+
* @property {myTypes.typeA} prop1 - Prop 1.
50+
* @property {string} prop2 - Prop 2.
51+
*/
52+
53+
/** @typedef {myTypes.typeB|Function} myTypes.typeC */
54+
55+
exports.myTypes = myTypes;

0 commit comments

Comments
 (0)