In JS declaration emit, move imports painted in nested contexts to the root private context (#39818)

* In JS declaration emit, move imports painted in nested contexts to the root private context

* Add test for nathan
This commit is contained in:
Wesley Wigham 2020-07-31 18:25:37 -07:00 committed by GitHub
parent 1b97d03b8a
commit 94989789df
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 665 additions and 7 deletions

View File

@ -5899,7 +5899,7 @@ namespace ts {
const enclosingDeclaration = context.enclosingDeclaration!;
let results: Statement[] = [];
const visitedSymbols = new Set<number>();
let deferredPrivates: ESMap<SymbolId, Symbol> | undefined;
const deferredPrivatesStack: ESMap<SymbolId, Symbol>[] = [];
const oldcontext = context;
context = {
...oldcontext,
@ -6110,9 +6110,8 @@ namespace ts {
}
function visitSymbolTable(symbolTable: SymbolTable, suppressNewPrivateContext?: boolean, propertyAsAlias?: boolean) {
const oldDeferredPrivates = deferredPrivates;
if (!suppressNewPrivateContext) {
deferredPrivates = new Map();
deferredPrivatesStack.push(new Map());
}
symbolTable.forEach((symbol: Symbol) => {
serializeSymbol(symbol, /*isPrivate*/ false, !!propertyAsAlias);
@ -6121,11 +6120,11 @@ namespace ts {
// deferredPrivates will be filled up by visiting the symbol table
// And will continue to iterate as elements are added while visited `deferredPrivates`
// (As that's how a map iterator is defined to work)
deferredPrivates!.forEach((symbol: Symbol) => {
deferredPrivatesStack[deferredPrivatesStack.length - 1].forEach((symbol: Symbol) => {
serializeSymbol(symbol, /*isPrivate*/ true, !!propertyAsAlias);
});
deferredPrivatesStack.pop();
}
deferredPrivates = oldDeferredPrivates;
}
function serializeSymbol(symbol: Symbol, isPrivate: boolean, propertyAsAlias: boolean) {
@ -6309,9 +6308,19 @@ namespace ts {
function includePrivateSymbol(symbol: Symbol) {
if (some(symbol.declarations, isParameterDeclaration)) return;
Debug.assertIsDefined(deferredPrivates);
Debug.assertIsDefined(deferredPrivatesStack[deferredPrivatesStack.length - 1]);
getUnusedName(unescapeLeadingUnderscores(symbol.escapedName), symbol); // Call to cache unique name for symbol
deferredPrivates.set(getSymbolId(symbol), symbol);
// Blanket moving (import) aliases into the root private context should work, since imports are not valid within namespaces
// (so they must have been in the root to begin with if they were real imports) cjs `require` aliases (an upcoming feature)
// will throw a wrench in this, since those may have been nested, but we'll need to synthesize them in the outer scope
// 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
// for the moved import; which hopefully the above `getUnusedName` call should produce.
const isExternalImportAlias = !!(symbol.flags & SymbolFlags.Alias) && !some(symbol.declarations, d =>
!!findAncestor(d, isExportDeclaration) ||
isNamespaceExport(d) ||
(isImportEqualsDeclaration(d) && !isExternalModuleReference(d.moduleReference))
);
deferredPrivatesStack[isExternalImportAlias ? 0 : (deferredPrivatesStack.length - 1)].set(getSymbolId(symbol), symbol);
}
function isExportingScope(enclosingDeclaration: Node) {

View File

@ -0,0 +1,99 @@
//// [tests/cases/conformance/jsdoc/declarations/jsDeclarationsImportAliasExposedWithinNamespace.ts] ////
//// [file.js]
/**
* @namespace myTypes
* @global
* @type {Object<string,*>}
*/
const myTypes = {
// SOME PROPS HERE
};
/** @typedef {string|RegExp|Array<string|RegExp>} myTypes.typeA */
/**
* @typedef myTypes.typeB
* @property {myTypes.typeA} prop1 - Prop 1.
* @property {string} prop2 - Prop 2.
*/
/** @typedef {myTypes.typeB|Function} myTypes.typeC */
export {myTypes};
//// [file2.js]
import {myTypes} from './file.js';
/**
* @namespace testFnTypes
* @global
* @type {Object<string,*>}
*/
const testFnTypes = {
// SOME PROPS HERE
};
/** @typedef {boolean|myTypes.typeC} testFnTypes.input */
/**
* @function testFn
* @description A test function.
* @param {testFnTypes.input} input - Input.
* @returns {number|null} Result.
*/
function testFn(input) {
if (typeof input === 'number') {
return 2 * input;
} else {
return null;
}
}
export {testFn, testFnTypes};
//// [file.d.ts]
/**
* @namespace myTypes
* @global
* @type {Object<string,*>}
*/
export const myTypes: {
[x: string]: any;
};
export namespace myTypes {
type typeA = string | RegExp | (string | RegExp)[];
type typeB = {
/**
* - Prop 1.
*/
prop1: string | RegExp | (string | RegExp)[];
/**
* - Prop 2.
*/
prop2: string;
};
type typeC = Function | typeB;
}
//// [file2.d.ts]
/** @typedef {boolean|myTypes.typeC} testFnTypes.input */
/**
* @function testFn
* @description A test function.
* @param {testFnTypes.input} input - Input.
* @returns {number|null} Result.
*/
export function testFn(input: boolean | Function | myTypes.typeB): number | null;
/**
* @namespace testFnTypes
* @global
* @type {Object<string,*>}
*/
export const testFnTypes: {
[x: string]: any;
};
export namespace testFnTypes {
type input = boolean | Function | myTypes.typeB;
}
import { myTypes } from "./file.js";

View File

@ -0,0 +1,67 @@
=== tests/cases/conformance/jsdoc/declarations/file.js ===
/**
* @namespace myTypes
* @global
* @type {Object<string,*>}
*/
const myTypes = {
>myTypes : Symbol(myTypes, Decl(file.js, 5, 5), Decl(file.js, 9, 50), Decl(file.js, 12, 12), Decl(file.js, 17, 38))
// SOME PROPS HERE
};
/** @typedef {string|RegExp|Array<string|RegExp>} myTypes.typeA */
/**
* @typedef myTypes.typeB
* @property {myTypes.typeA} prop1 - Prop 1.
* @property {string} prop2 - Prop 2.
*/
/** @typedef {myTypes.typeB|Function} myTypes.typeC */
export {myTypes};
>myTypes : Symbol(myTypes, Decl(file.js, 19, 8))
=== tests/cases/conformance/jsdoc/declarations/file2.js ===
import {myTypes} from './file.js';
>myTypes : Symbol(myTypes, Decl(file2.js, 0, 8))
/**
* @namespace testFnTypes
* @global
* @type {Object<string,*>}
*/
const testFnTypes = {
>testFnTypes : Symbol(testFnTypes, Decl(file2.js, 7, 5), Decl(file2.js, 11, 37))
// SOME PROPS HERE
};
/** @typedef {boolean|myTypes.typeC} testFnTypes.input */
/**
* @function testFn
* @description A test function.
* @param {testFnTypes.input} input - Input.
* @returns {number|null} Result.
*/
function testFn(input) {
>testFn : Symbol(testFn, Decl(file2.js, 9, 2))
>input : Symbol(input, Decl(file2.js, 19, 16))
if (typeof input === 'number') {
>input : Symbol(input, Decl(file2.js, 19, 16))
return 2 * input;
>input : Symbol(input, Decl(file2.js, 19, 16))
} else {
return null;
}
}
export {testFn, testFnTypes};
>testFn : Symbol(testFn, Decl(file2.js, 27, 8))
>testFnTypes : Symbol(testFnTypes, Decl(file2.js, 27, 15))

View File

@ -0,0 +1,75 @@
=== tests/cases/conformance/jsdoc/declarations/file.js ===
/**
* @namespace myTypes
* @global
* @type {Object<string,*>}
*/
const myTypes = {
>myTypes : { [x: string]: any; }
>{ // SOME PROPS HERE} : {}
// SOME PROPS HERE
};
/** @typedef {string|RegExp|Array<string|RegExp>} myTypes.typeA */
/**
* @typedef myTypes.typeB
* @property {myTypes.typeA} prop1 - Prop 1.
* @property {string} prop2 - Prop 2.
*/
/** @typedef {myTypes.typeB|Function} myTypes.typeC */
export {myTypes};
>myTypes : { [x: string]: any; }
=== tests/cases/conformance/jsdoc/declarations/file2.js ===
import {myTypes} from './file.js';
>myTypes : { [x: string]: any; }
/**
* @namespace testFnTypes
* @global
* @type {Object<string,*>}
*/
const testFnTypes = {
>testFnTypes : { [x: string]: any; }
>{ // SOME PROPS HERE} : {}
// SOME PROPS HERE
};
/** @typedef {boolean|myTypes.typeC} testFnTypes.input */
/**
* @function testFn
* @description A test function.
* @param {testFnTypes.input} input - Input.
* @returns {number|null} Result.
*/
function testFn(input) {
>testFn : (input: testFnTypes.input) => number | null
>input : boolean | Function | myTypes.typeB
if (typeof input === 'number') {
>typeof input === 'number' : boolean
>typeof input : "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function"
>input : boolean | Function | myTypes.typeB
>'number' : "number"
return 2 * input;
>2 * input : number
>2 : 2
>input : never
} else {
return null;
>null : null
}
}
export {testFn, testFnTypes};
>testFn : (input: boolean | Function | myTypes.typeB) => number
>testFnTypes : { [x: string]: any; }

View File

@ -0,0 +1,55 @@
tests/cases/conformance/jsdoc/declarations/file2.js(12,31): error TS2694: Namespace 'myTypes' has no exported member 'typeC'.
==== tests/cases/conformance/jsdoc/declarations/file2.js (1 errors) ====
const {myTypes} = require('./file.js');
/**
* @namespace testFnTypes
* @global
* @type {Object<string,*>}
*/
const testFnTypes = {
// SOME PROPS HERE
};
/** @typedef {boolean|myTypes.typeC} testFnTypes.input */
~~~~~
!!! error TS2694: Namespace 'myTypes' has no exported member 'typeC'.
/**
* @function testFn
* @description A test function.
* @param {testFnTypes.input} input - Input.
* @returns {number|null} Result.
*/
function testFn(input) {
if (typeof input === 'number') {
return 2 * input;
} else {
return null;
}
}
module.exports = {testFn, testFnTypes};
==== tests/cases/conformance/jsdoc/declarations/file.js (0 errors) ====
/**
* @namespace myTypes
* @global
* @type {Object<string,*>}
*/
const myTypes = {
// SOME PROPS HERE
};
/** @typedef {string|RegExp|Array<string|RegExp>} myTypes.typeA */
/**
* @typedef myTypes.typeB
* @property {myTypes.typeA} prop1 - Prop 1.
* @property {string} prop2 - Prop 2.
*/
/** @typedef {myTypes.typeB|Function} myTypes.typeC */
exports.myTypes = myTypes;

View File

@ -0,0 +1,79 @@
//// [tests/cases/conformance/jsdoc/declarations/jsDeclarationsImportAliasExposedWithinNamespaceCjs.ts] ////
//// [file.js]
/**
* @namespace myTypes
* @global
* @type {Object<string,*>}
*/
const myTypes = {
// SOME PROPS HERE
};
/** @typedef {string|RegExp|Array<string|RegExp>} myTypes.typeA */
/**
* @typedef myTypes.typeB
* @property {myTypes.typeA} prop1 - Prop 1.
* @property {string} prop2 - Prop 2.
*/
/** @typedef {myTypes.typeB|Function} myTypes.typeC */
exports.myTypes = myTypes;
//// [file2.js]
const {myTypes} = require('./file.js');
/**
* @namespace testFnTypes
* @global
* @type {Object<string,*>}
*/
const testFnTypes = {
// SOME PROPS HERE
};
/** @typedef {boolean|myTypes.typeC} testFnTypes.input */
/**
* @function testFn
* @description A test function.
* @param {testFnTypes.input} input - Input.
* @returns {number|null} Result.
*/
function testFn(input) {
if (typeof input === 'number') {
return 2 * input;
} else {
return null;
}
}
module.exports = {testFn, testFnTypes};
//// [file.d.ts]
export var myTypes: {
[x: string]: any;
};
//// [file2.d.ts]
/** @typedef {boolean|myTypes.typeC} testFnTypes.input */
/**
* @function testFn
* @description A test function.
* @param {testFnTypes.input} input - Input.
* @returns {number|null} Result.
*/
export function testFn(input: any): number | null;
/**
* @namespace testFnTypes
* @global
* @type {Object<string,*>}
*/
export const testFnTypes: {
[x: string]: any;
};
export namespace testFnTypes {
type input = any;
}

View File

@ -0,0 +1,75 @@
=== tests/cases/conformance/jsdoc/declarations/file2.js ===
const {myTypes} = require('./file.js');
>myTypes : Symbol(myTypes, Decl(file2.js, 0, 7))
>require : Symbol(require)
>'./file.js' : Symbol("tests/cases/conformance/jsdoc/declarations/file", Decl(file.js, 0, 0))
/**
* @namespace testFnTypes
* @global
* @type {Object<string,*>}
*/
const testFnTypes = {
>testFnTypes : Symbol(testFnTypes, Decl(file2.js, 7, 5), Decl(file2.js, 11, 37))
// SOME PROPS HERE
};
/** @typedef {boolean|myTypes.typeC} testFnTypes.input */
/**
* @function testFn
* @description A test function.
* @param {testFnTypes.input} input - Input.
* @returns {number|null} Result.
*/
function testFn(input) {
>testFn : Symbol(testFn, Decl(file2.js, 9, 2))
>input : Symbol(input, Decl(file2.js, 19, 16))
if (typeof input === 'number') {
>input : Symbol(input, Decl(file2.js, 19, 16))
return 2 * input;
>input : Symbol(input, Decl(file2.js, 19, 16))
} else {
return null;
}
}
module.exports = {testFn, testFnTypes};
>module.exports : Symbol("tests/cases/conformance/jsdoc/declarations/file2", Decl(file2.js, 0, 0))
>module : Symbol(export=, Decl(file2.js, 25, 1))
>exports : Symbol(export=, Decl(file2.js, 25, 1))
>testFn : Symbol(testFn, Decl(file2.js, 27, 18))
>testFnTypes : Symbol(testFnTypes, Decl(file2.js, 27, 25))
=== tests/cases/conformance/jsdoc/declarations/file.js ===
/**
* @namespace myTypes
* @global
* @type {Object<string,*>}
*/
const myTypes = {
>myTypes : Symbol(myTypes, Decl(file.js, 5, 5), Decl(file.js, 9, 50), Decl(file.js, 12, 12), Decl(file.js, 17, 38))
// SOME PROPS HERE
};
/** @typedef {string|RegExp|Array<string|RegExp>} myTypes.typeA */
/**
* @typedef myTypes.typeB
* @property {myTypes.typeA} prop1 - Prop 1.
* @property {string} prop2 - Prop 2.
*/
/** @typedef {myTypes.typeB|Function} myTypes.typeC */
exports.myTypes = myTypes;
>exports.myTypes : Symbol(myTypes, Decl(file.js, 7, 2))
>exports : Symbol(myTypes, Decl(file.js, 7, 2))
>myTypes : Symbol(myTypes, Decl(file.js, 7, 2))
>myTypes : Symbol(myTypes, Decl(file.js, 5, 5), Decl(file.js, 9, 50), Decl(file.js, 12, 12), Decl(file.js, 17, 38))

View File

@ -0,0 +1,87 @@
=== tests/cases/conformance/jsdoc/declarations/file2.js ===
const {myTypes} = require('./file.js');
>myTypes : { [x: string]: any; }
>require('./file.js') : typeof import("tests/cases/conformance/jsdoc/declarations/file")
>require : any
>'./file.js' : "./file.js"
/**
* @namespace testFnTypes
* @global
* @type {Object<string,*>}
*/
const testFnTypes = {
>testFnTypes : { [x: string]: any; }
>{ // SOME PROPS HERE} : {}
// SOME PROPS HERE
};
/** @typedef {boolean|myTypes.typeC} testFnTypes.input */
/**
* @function testFn
* @description A test function.
* @param {testFnTypes.input} input - Input.
* @returns {number|null} Result.
*/
function testFn(input) {
>testFn : (input: testFnTypes.input) => number | null
>input : any
if (typeof input === 'number') {
>typeof input === 'number' : boolean
>typeof input : "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function"
>input : any
>'number' : "number"
return 2 * input;
>2 * input : number
>2 : 2
>input : number
} else {
return null;
>null : null
}
}
module.exports = {testFn, testFnTypes};
>module.exports = {testFn, testFnTypes} : { testFn: (input: any) => number; testFnTypes: { [x: string]: any; }; }
>module.exports : { testFn: (input: any) => number; testFnTypes: { [x: string]: any; }; }
>module : { "\"tests/cases/conformance/jsdoc/declarations/file2\"": { testFn: (input: any) => number; testFnTypes: { [x: string]: any; }; }; }
>exports : { testFn: (input: any) => number; testFnTypes: { [x: string]: any; }; }
>{testFn, testFnTypes} : { testFn: (input: any) => number; testFnTypes: { [x: string]: any; }; }
>testFn : (input: any) => number
>testFnTypes : { [x: string]: any; }
=== tests/cases/conformance/jsdoc/declarations/file.js ===
/**
* @namespace myTypes
* @global
* @type {Object<string,*>}
*/
const myTypes = {
>myTypes : { [x: string]: any; }
>{ // SOME PROPS HERE} : {}
// SOME PROPS HERE
};
/** @typedef {string|RegExp|Array<string|RegExp>} myTypes.typeA */
/**
* @typedef myTypes.typeB
* @property {myTypes.typeA} prop1 - Prop 1.
* @property {string} prop2 - Prop 2.
*/
/** @typedef {myTypes.typeB|Function} myTypes.typeC */
exports.myTypes = myTypes;
>exports.myTypes = myTypes : { [x: string]: any; }
>exports.myTypes : { [x: string]: any; }
>exports : typeof import("tests/cases/conformance/jsdoc/declarations/file")
>myTypes : { [x: string]: any; }
>myTypes : { [x: string]: any; }

View File

@ -0,0 +1,56 @@
// @declaration: true
// @emitDeclarationOnly: true
// @allowJs: true
// @checkJs: true
// @module: commonjs
// @target: es6
// @filename: file.js
/**
* @namespace myTypes
* @global
* @type {Object<string,*>}
*/
const myTypes = {
// SOME PROPS HERE
};
/** @typedef {string|RegExp|Array<string|RegExp>} myTypes.typeA */
/**
* @typedef myTypes.typeB
* @property {myTypes.typeA} prop1 - Prop 1.
* @property {string} prop2 - Prop 2.
*/
/** @typedef {myTypes.typeB|Function} myTypes.typeC */
export {myTypes};
// @filename: file2.js
import {myTypes} from './file.js';
/**
* @namespace testFnTypes
* @global
* @type {Object<string,*>}
*/
const testFnTypes = {
// SOME PROPS HERE
};
/** @typedef {boolean|myTypes.typeC} testFnTypes.input */
/**
* @function testFn
* @description A test function.
* @param {testFnTypes.input} input - Input.
* @returns {number|null} Result.
*/
function testFn(input) {
if (typeof input === 'number') {
return 2 * input;
} else {
return null;
}
}
export {testFn, testFnTypes};

View File

@ -0,0 +1,56 @@
// @declaration: true
// @emitDeclarationOnly: true
// @allowJs: true
// @checkJs: true
// @module: commonjs
// @target: es6
// @filename: file.js
/**
* @namespace myTypes
* @global
* @type {Object<string,*>}
*/
const myTypes = {
// SOME PROPS HERE
};
/** @typedef {string|RegExp|Array<string|RegExp>} myTypes.typeA */
/**
* @typedef myTypes.typeB
* @property {myTypes.typeA} prop1 - Prop 1.
* @property {string} prop2 - Prop 2.
*/
/** @typedef {myTypes.typeB|Function} myTypes.typeC */
exports.myTypes = myTypes;
// @filename: file2.js
const {myTypes} = require('./file.js');
/**
* @namespace testFnTypes
* @global
* @type {Object<string,*>}
*/
const testFnTypes = {
// SOME PROPS HERE
};
/** @typedef {boolean|myTypes.typeC} testFnTypes.input */
/**
* @function testFn
* @description A test function.
* @param {testFnTypes.input} input - Input.
* @returns {number|null} Result.
*/
function testFn(input) {
if (typeof input === 'number') {
return 2 * input;
} else {
return null;
}
}
module.exports = {testFn, testFnTypes};