From 115884aa30e23b751981585f0121e29a9ebd276a Mon Sep 17 00:00:00 2001 From: Ron Buckton Date: Wed, 21 Jun 2017 18:20:46 -0700 Subject: [PATCH 01/26] Follow symbol through commonjs require for inferred class type --- src/compiler/checker.ts | 31 ++++++++--- .../reference/constructorFunctions2.symbols | 41 ++++++++++++++ .../reference/constructorFunctions2.types | 55 +++++++++++++++++++ .../salsa/constructorFunctions2.ts | 18 ++++++ 4 files changed, 136 insertions(+), 9 deletions(-) create mode 100644 tests/baselines/reference/constructorFunctions2.symbols create mode 100644 tests/baselines/reference/constructorFunctions2.types create mode 100644 tests/cases/conformance/salsa/constructorFunctions2.ts diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 05c20ffb11a..29b0420201e 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -16073,8 +16073,8 @@ namespace ts { * Indicates whether a declaration can be treated as a constructor in a JavaScript * file. */ - function isJavaScriptConstructor(node: Declaration): boolean { - if (isInJavaScriptFile(node)) { + function isJavaScriptConstructor(node: Declaration | undefined): boolean { + if (node && isInJavaScriptFile(node)) { // If the node has a @class tag, treat it like a constructor. if (getJSDocClassTag(node)) return true; @@ -16089,6 +16089,21 @@ namespace ts { return false; } + function getJavaScriptClassType(symbol: Symbol): Type | undefined { + if (symbol && isDeclarationOfFunctionOrClassExpression(symbol)) { + symbol = getSymbolOfNode((symbol.valueDeclaration).initializer); + } + if (isJavaScriptConstructor(symbol.valueDeclaration)) { + return getInferredClassType(symbol); + } + if (symbol.flags & SymbolFlags.Variable) { + const valueType = getTypeOfSymbol(symbol); + if (valueType.symbol && !isInferredClassType(valueType) && isJavaScriptConstructor(valueType.symbol.valueDeclaration)) { + return getInferredClassType(valueType.symbol); + } + } + } + function getInferredClassType(symbol: Symbol) { const links = getSymbolLinks(symbol); if (!links.inferredClassType) { @@ -16132,16 +16147,14 @@ namespace ts { // in a JS file // Note:JS inferred classes might come from a variable declaration instead of a function declaration. // In this case, using getResolvedSymbol directly is required to avoid losing the members from the declaration. - let funcSymbol = node.expression.kind === SyntaxKind.Identifier ? + const funcSymbol = node.expression.kind === SyntaxKind.Identifier ? getResolvedSymbol(node.expression as Identifier) : checkExpression(node.expression).symbol; - if (funcSymbol && isDeclarationOfFunctionOrClassExpression(funcSymbol)) { - funcSymbol = getSymbolOfNode((funcSymbol.valueDeclaration).initializer); + const type = funcSymbol && getJavaScriptClassType(funcSymbol); + if (type) { + return type; } - if (funcSymbol && funcSymbol.flags & SymbolFlags.Function && (funcSymbol.members || getJSDocClassTag(funcSymbol.valueDeclaration))) { - return getInferredClassType(funcSymbol); - } - else if (noImplicitAny) { + if (noImplicitAny) { error(node, Diagnostics.new_expression_whose_target_lacks_a_construct_signature_implicitly_has_an_any_type); } return anyType; diff --git a/tests/baselines/reference/constructorFunctions2.symbols b/tests/baselines/reference/constructorFunctions2.symbols new file mode 100644 index 00000000000..1046aa32e9a --- /dev/null +++ b/tests/baselines/reference/constructorFunctions2.symbols @@ -0,0 +1,41 @@ +=== tests/cases/conformance/salsa/node.d.ts === +declare function require(id: string): any; +>require : Symbol(require, Decl(node.d.ts, 0, 0)) +>id : Symbol(id, Decl(node.d.ts, 0, 25)) + +declare var module: any, exports: any; +>module : Symbol(module, Decl(node.d.ts, 1, 11)) +>exports : Symbol(exports, Decl(node.d.ts, 1, 24)) + +=== tests/cases/conformance/salsa/index.js === +const A = require("./other"); +>A : Symbol(A, Decl(index.js, 0, 5)) +>require : Symbol(require, Decl(node.d.ts, 0, 0)) +>"./other" : Symbol("tests/cases/conformance/salsa/other", Decl(other.js, 0, 0)) + +const a = new A().id; +>a : Symbol(a, Decl(index.js, 1, 5)) +>new A().id : Symbol(A.id, Decl(other.js, 0, 14)) +>A : Symbol(A, Decl(index.js, 0, 5)) +>id : Symbol(A.id, Decl(other.js, 0, 14)) + +const B = function() { this.id = 1; } +>B : Symbol(B, Decl(index.js, 3, 5)) +>id : Symbol(B.id, Decl(index.js, 3, 22)) + +const b = new B().id; +>b : Symbol(b, Decl(index.js, 4, 5)) +>new B().id : Symbol(B.id, Decl(index.js, 3, 22)) +>B : Symbol(B, Decl(index.js, 3, 5)) +>id : Symbol(B.id, Decl(index.js, 3, 22)) + +=== tests/cases/conformance/salsa/other.js === +function A() { this.id = 1; } +>A : Symbol(A, Decl(other.js, 0, 0)) +>id : Symbol(A.id, Decl(other.js, 0, 14)) + +module.exports = A; +>module : Symbol(export=, Decl(other.js, 0, 29)) +>exports : Symbol(export=, Decl(other.js, 0, 29)) +>A : Symbol(A, Decl(other.js, 0, 0)) + diff --git a/tests/baselines/reference/constructorFunctions2.types b/tests/baselines/reference/constructorFunctions2.types new file mode 100644 index 00000000000..e96053b4f49 --- /dev/null +++ b/tests/baselines/reference/constructorFunctions2.types @@ -0,0 +1,55 @@ +=== tests/cases/conformance/salsa/node.d.ts === +declare function require(id: string): any; +>require : (id: string) => any +>id : string + +declare var module: any, exports: any; +>module : any +>exports : any + +=== tests/cases/conformance/salsa/index.js === +const A = require("./other"); +>A : () => void +>require("./other") : () => void +>require : (id: string) => any +>"./other" : "./other" + +const a = new A().id; +>a : number +>new A().id : number +>new A() : { id: number; } +>A : () => void +>id : number + +const B = function() { this.id = 1; } +>B : () => void +>function() { this.id = 1; } : () => void +>this.id = 1 : 1 +>this.id : any +>this : any +>id : any +>1 : 1 + +const b = new B().id; +>b : number +>new B().id : number +>new B() : { id: number; } +>B : () => void +>id : number + +=== tests/cases/conformance/salsa/other.js === +function A() { this.id = 1; } +>A : () => void +>this.id = 1 : 1 +>this.id : any +>this : any +>id : any +>1 : 1 + +module.exports = A; +>module.exports = A : () => void +>module.exports : any +>module : any +>exports : any +>A : () => void + diff --git a/tests/cases/conformance/salsa/constructorFunctions2.ts b/tests/cases/conformance/salsa/constructorFunctions2.ts new file mode 100644 index 00000000000..13a6e7b9a3a --- /dev/null +++ b/tests/cases/conformance/salsa/constructorFunctions2.ts @@ -0,0 +1,18 @@ +// @allowJs: true +// @checkJs: true +// @noEmit: true +// @module: commonjs +// @filename: node.d.ts +declare function require(id: string): any; +declare var module: any, exports: any; + +// @filename: index.js +const A = require("./other"); +const a = new A().id; + +const B = function() { this.id = 1; } +const b = new B().id; + +// @filename: other.js +function A() { this.id = 1; } +module.exports = A; \ No newline at end of file From 8ca66d318061f6b95d90293413fea98189d84e4d Mon Sep 17 00:00:00 2001 From: Ron Buckton Date: Fri, 11 Aug 2017 16:15:28 -0700 Subject: [PATCH 02/26] PR feedback --- src/compiler/checker.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 29b0420201e..e0ef3ca82ba 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -16090,7 +16090,7 @@ namespace ts { } function getJavaScriptClassType(symbol: Symbol): Type | undefined { - if (symbol && isDeclarationOfFunctionOrClassExpression(symbol)) { + if (isDeclarationOfFunctionOrClassExpression(symbol)) { symbol = getSymbolOfNode((symbol.valueDeclaration).initializer); } if (isJavaScriptConstructor(symbol.valueDeclaration)) { From 5665c098dab4429eb66744884315bef80b5e4486 Mon Sep 17 00:00:00 2001 From: Paul van Brenk Date: Mon, 14 Aug 2017 16:55:37 -0700 Subject: [PATCH 03/26] Introduce the concept of a Dynamic File Dynamic files are generally created by the debugger when while debugging it can't find a matching file on disk. Since these files don't exist on disk, we shouldn't check if the file exists on disk, and allow the content to be controlled by the host. --- src/server/builder.ts | 6 +++--- src/server/editorServices.ts | 28 +++++++++++++++++----------- src/server/scriptInfo.ts | 11 ++++++----- src/server/utilities.ts | 8 ++++---- 4 files changed, 30 insertions(+), 23 deletions(-) diff --git a/src/server/builder.ts b/src/server/builder.ts index 8a10682b4cc..5cf65611fb3 100644 --- a/src/server/builder.ts +++ b/src/server/builder.ts @@ -5,7 +5,7 @@ namespace ts.server { export function shouldEmitFile(scriptInfo: ScriptInfo) { - return !scriptInfo.hasMixedContent; + return !scriptInfo.hasMixedContent && !scriptInfo.isDynamic; } /** @@ -188,7 +188,7 @@ namespace ts.server { */ getFilesAffectedBy(scriptInfo: ScriptInfo): string[] { const info = this.getOrCreateFileInfo(scriptInfo.path); - const singleFileResult = scriptInfo.hasMixedContent ? [] : [scriptInfo.fileName]; + const singleFileResult = scriptInfo.hasMixedContent || scriptInfo.isDynamic ? [] : [scriptInfo.fileName]; if (info.updateShapeSignature()) { const options = this.project.getCompilerOptions(); // If `--out` or `--outFile` is specified, any new emit will result in re-emitting the entire project, @@ -303,7 +303,7 @@ namespace ts.server { getFilesAffectedBy(scriptInfo: ScriptInfo): string[] { this.ensureProjectDependencyGraphUpToDate(); - const singleFileResult = scriptInfo.hasMixedContent ? [] : [scriptInfo.fileName]; + const singleFileResult = scriptInfo.hasMixedContent || scriptInfo.isDynamic ? [] : [scriptInfo.fileName]; const fileInfo = this.getFileInfo(scriptInfo.path); if (!fileInfo || !fileInfo.updateShapeSignature()) { return singleFileResult; diff --git a/src/server/editorServices.ts b/src/server/editorServices.ts index fe7946fc0f7..6d0f8d41e61 100644 --- a/src/server/editorServices.ts +++ b/src/server/editorServices.ts @@ -234,18 +234,21 @@ namespace ts.server { getFileName(f: T): string; getScriptKind(f: T): ScriptKind; hasMixedContent(f: T, extraFileExtensions: JsFileExtensionInfo[]): boolean; + isDynamicFile(f: T): boolean; } const fileNamePropertyReader: FilePropertyReader = { getFileName: x => x, getScriptKind: _ => undefined, hasMixedContent: (fileName, extraFileExtensions) => some(extraFileExtensions, ext => ext.isMixedContent && fileExtensionIs(fileName, ext.extension)), + isDynamicFile: x => x[0] == '^', }; const externalFilePropertyReader: FilePropertyReader = { getFileName: x => x.fileName, getScriptKind: x => tryConvertScriptKindName(x.scriptKind), - hasMixedContent: x => x.hasMixedContent + hasMixedContent: x => x.hasMixedContent, + isDynamicFile: x => x.fileName[0] == '^', }; function findProjectByName(projectName: string, projects: T[]): T { @@ -1177,15 +1180,16 @@ namespace ts.server { private addFilesToProjectAndUpdateGraph(project: ConfiguredProject | ExternalProject, files: T[], propertyReader: FilePropertyReader, clientFileName: string, typeAcquisition: TypeAcquisition, configFileErrors: ReadonlyArray): void { let errors: Diagnostic[]; for (const f of files) { - const rootFilename = propertyReader.getFileName(f); + const rootFileName = propertyReader.getFileName(f); const scriptKind = propertyReader.getScriptKind(f); const hasMixedContent = propertyReader.hasMixedContent(f, this.hostConfiguration.extraFileExtensions); - if (this.host.fileExists(rootFilename)) { - const info = this.getOrCreateScriptInfoForNormalizedPath(toNormalizedPath(rootFilename), /*openedByClient*/ clientFileName === rootFilename, /*fileContent*/ undefined, scriptKind, hasMixedContent); + const isDynamicFile = propertyReader.isDynamicFile(f); + if (isDynamicFile || this.host.fileExists(rootFileName)) { + const info = this.getOrCreateScriptInfoForNormalizedPath(toNormalizedPath(rootFileName), /*openedByClient*/ clientFileName === rootFileName, /*fileContent*/ undefined, scriptKind, hasMixedContent, isDynamicFile); project.addRoot(info); } else { - (errors || (errors = [])).push(createFileNotFoundDiagnostic(rootFilename)); + (errors || (errors = [])).push(createFileNotFoundDiagnostic(rootFileName)); } } project.setProjectErrors(concatenate(configFileErrors, errors)); @@ -1215,7 +1219,8 @@ namespace ts.server { let rootFilesChanged = false; for (const f of newUncheckedFiles) { const newRootFile = propertyReader.getFileName(f); - if (!this.host.fileExists(newRootFile)) { + const isDynamic = propertyReader.isDynamicFile(f); + if (!isDynamic && !this.host.fileExists(newRootFile)) { (projectErrors || (projectErrors = [])).push(createFileNotFoundDiagnostic(newRootFile)); continue; } @@ -1226,7 +1231,7 @@ namespace ts.server { if (!scriptInfo) { const scriptKind = propertyReader.getScriptKind(f); const hasMixedContent = propertyReader.hasMixedContent(f, this.hostConfiguration.extraFileExtensions); - scriptInfo = this.getOrCreateScriptInfoForNormalizedPath(normalizedPath, /*openedByClient*/ false, /*fileContent*/ undefined, scriptKind, hasMixedContent); + scriptInfo = this.getOrCreateScriptInfoForNormalizedPath(normalizedPath, /*openedByClient*/ false, /*fileContent*/ undefined, scriptKind, hasMixedContent, isDynamic); } } newRootScriptInfos.push(scriptInfo); @@ -1410,17 +1415,17 @@ namespace ts.server { watchClosedScriptInfo(info: ScriptInfo) { // do not watch files with mixed content - server doesn't know how to interpret it - if (!info.hasMixedContent) { + if (!info.hasMixedContent && !info.isDynamic) { const { fileName } = info; info.setWatcher(this.host.watchFile(fileName, _ => this.onSourceFileChanged(fileName))); } } - getOrCreateScriptInfoForNormalizedPath(fileName: NormalizedPath, openedByClient: boolean, fileContent?: string, scriptKind?: ScriptKind, hasMixedContent?: boolean) { + getOrCreateScriptInfoForNormalizedPath(fileName: NormalizedPath, openedByClient: boolean, fileContent?: string, scriptKind?: ScriptKind, hasMixedContent?: boolean, isDynamic?: boolean) { let info = this.getScriptInfoForNormalizedPath(fileName); if (!info) { - if (openedByClient || this.host.fileExists(fileName)) { - info = new ScriptInfo(this.host, fileName, scriptKind, hasMixedContent); + if (openedByClient || isDynamic || this.host.fileExists(fileName)) { + info = new ScriptInfo(this.host, fileName, scriptKind, hasMixedContent, isDynamic); this.filenameToScriptInfo.set(info.path, info); @@ -1430,6 +1435,7 @@ namespace ts.server { fileContent = this.host.readFile(fileName) || ""; } } + else { this.watchClosedScriptInfo(info); } diff --git a/src/server/scriptInfo.ts b/src/server/scriptInfo.ts index 4d73fc47403..a8ba9f5d4c2 100644 --- a/src/server/scriptInfo.ts +++ b/src/server/scriptInfo.ts @@ -156,11 +156,12 @@ namespace ts.server { private readonly host: ServerHost, readonly fileName: NormalizedPath, readonly scriptKind: ScriptKind, - public hasMixedContent = false) { + public hasMixedContent = false, + public isDynamic = false) { this.path = toPath(fileName, host.getCurrentDirectory(), createGetCanonicalFileName(host.useCaseSensitiveFileNames)); this.textStorage = new TextStorage(host, fileName); - if (hasMixedContent) { + if (hasMixedContent || isDynamic) { this.textStorage.reload(""); } this.scriptKind = scriptKind @@ -180,7 +181,7 @@ namespace ts.server { public close() { this.isOpen = false; - this.textStorage.useText(this.hasMixedContent ? "" : undefined); + this.textStorage.useText(this.hasMixedContent || this.isDynamic ? "" : undefined); this.markContainingProjectsAsDirty(); } @@ -307,7 +308,7 @@ namespace ts.server { } reloadFromFile(tempFileName?: NormalizedPath) { - if (this.hasMixedContent) { + if (this.hasMixedContent || this.isDynamic) { this.reload(""); } else { @@ -354,4 +355,4 @@ namespace ts.server { return this.scriptKind === ScriptKind.JS || this.scriptKind === ScriptKind.JSX; } } -} \ No newline at end of file +} diff --git a/src/server/utilities.ts b/src/server/utilities.ts index 0d4bc101ff6..7a5dbdf7cc7 100644 --- a/src/server/utilities.ts +++ b/src/server/utilities.ts @@ -84,7 +84,7 @@ namespace ts.server { }; } - export function mergeMapLikes(target: MapLike, source: MapLike ): void { + export function mergeMapLikes(target: MapLike, source: MapLike): void { for (const key in source) { if (hasProperty(source, key)) { target[key] = source[key]; @@ -115,9 +115,9 @@ namespace ts.server { } export function createNormalizedPathMap(): NormalizedPathMap { -/* tslint:disable:no-null-keyword */ + /* tslint:disable:no-null-keyword */ const map = createMap(); -/* tslint:enable:no-null-keyword */ + /* tslint:enable:no-null-keyword */ return { get(path) { return map.get(path); @@ -290,4 +290,4 @@ namespace ts.server { deleted(oldItems[oldIndex++]); } } -} \ No newline at end of file +} From b07fd3e1809fcde8f8360349b913cedf838181d9 Mon Sep 17 00:00:00 2001 From: Paul van Brenk Date: Tue, 15 Aug 2017 12:27:02 -0700 Subject: [PATCH 04/26] Fix linter issues --- src/server/editorServices.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/server/editorServices.ts b/src/server/editorServices.ts index 6d0f8d41e61..19953c9a79b 100644 --- a/src/server/editorServices.ts +++ b/src/server/editorServices.ts @@ -241,14 +241,14 @@ namespace ts.server { getFileName: x => x, getScriptKind: _ => undefined, hasMixedContent: (fileName, extraFileExtensions) => some(extraFileExtensions, ext => ext.isMixedContent && fileExtensionIs(fileName, ext.extension)), - isDynamicFile: x => x[0] == '^', + isDynamicFile: x => x[0] === "^", }; const externalFilePropertyReader: FilePropertyReader = { getFileName: x => x.fileName, getScriptKind: x => tryConvertScriptKindName(x.scriptKind), hasMixedContent: x => x.hasMixedContent, - isDynamicFile: x => x.fileName[0] == '^', + isDynamicFile: x => x.fileName[0] === "^", }; function findProjectByName(projectName: string, projects: T[]): T { From 26a02860b0e99699e5851b62ff4f3d761bb7780a Mon Sep 17 00:00:00 2001 From: Ron Buckton Date: Wed, 23 Aug 2017 17:09:47 -0700 Subject: [PATCH 05/26] Fix crash when exporting class without name --- src/compiler/transformers/module/module.ts | 2 +- src/compiler/transformers/utilities.ts | 2 +- .../reference/exportClassWithoutName.errors.txt | 8 ++++++++ tests/baselines/reference/exportClassWithoutName.js | 10 ++++++++++ tests/cases/compiler/exportClassWithoutName.ts | 4 ++++ 5 files changed, 24 insertions(+), 2 deletions(-) create mode 100644 tests/baselines/reference/exportClassWithoutName.errors.txt create mode 100644 tests/baselines/reference/exportClassWithoutName.js create mode 100644 tests/cases/compiler/exportClassWithoutName.ts diff --git a/src/compiler/transformers/module/module.ts b/src/compiler/transformers/module/module.ts index 2c3133de6f5..a0f593e5111 100644 --- a/src/compiler/transformers/module/module.ts +++ b/src/compiler/transformers/module/module.ts @@ -1204,7 +1204,7 @@ namespace ts { } if (hasModifier(decl, ModifierFlags.Export)) { - const exportName = hasModifier(decl, ModifierFlags.Default) ? createIdentifier("default") : decl.name; + const exportName = hasModifier(decl, ModifierFlags.Default) ? createIdentifier("default") : getDeclarationName(decl); statements = appendExportStatement(statements, exportName, getLocalName(decl), /*location*/ decl); } diff --git a/src/compiler/transformers/utilities.ts b/src/compiler/transformers/utilities.ts index be99df62b67..f641ee49faf 100644 --- a/src/compiler/transformers/utilities.ts +++ b/src/compiler/transformers/utilities.ts @@ -124,7 +124,7 @@ namespace ts { else { // export class x { } const name = (node).name; - if (!uniqueExports.get(unescapeLeadingUnderscores(name.escapedText))) { + if (name && !uniqueExports.get(unescapeLeadingUnderscores(name.escapedText))) { multiMapSparseArrayAdd(exportedBindings, getOriginalNodeId(node), name); uniqueExports.set(unescapeLeadingUnderscores(name.escapedText), true); exportedNames = append(exportedNames, name); diff --git a/tests/baselines/reference/exportClassWithoutName.errors.txt b/tests/baselines/reference/exportClassWithoutName.errors.txt new file mode 100644 index 00000000000..448a1b2bb03 --- /dev/null +++ b/tests/baselines/reference/exportClassWithoutName.errors.txt @@ -0,0 +1,8 @@ +tests/cases/compiler/exportClassWithoutName.ts(1,1): error TS1211: A class declaration without the 'default' modifier must have a name. + + +==== tests/cases/compiler/exportClassWithoutName.ts (1 errors) ==== + export class { + ~~~~~~ +!!! error TS1211: A class declaration without the 'default' modifier must have a name. + } \ No newline at end of file diff --git a/tests/baselines/reference/exportClassWithoutName.js b/tests/baselines/reference/exportClassWithoutName.js new file mode 100644 index 00000000000..45b4301a812 --- /dev/null +++ b/tests/baselines/reference/exportClassWithoutName.js @@ -0,0 +1,10 @@ +//// [exportClassWithoutName.ts] +export class { +} + +//// [exportClassWithoutName.js] +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +class default_1 { +} +exports.default_1 = default_1; diff --git a/tests/cases/compiler/exportClassWithoutName.ts b/tests/cases/compiler/exportClassWithoutName.ts new file mode 100644 index 00000000000..a83ca9758c7 --- /dev/null +++ b/tests/cases/compiler/exportClassWithoutName.ts @@ -0,0 +1,4 @@ +//@module: commonjs +//@target: es2015 +export class { +} \ No newline at end of file From e2141ad4694c3dc4d93388d5ee0d8bb0c7cbaa84 Mon Sep 17 00:00:00 2001 From: Andy Date: Thu, 24 Aug 2017 09:55:01 -0700 Subject: [PATCH 06/26] Mark some arrays as readonly (#17725) * Mark some arrays as readonly * Avoid unnecessary allocations and style changes * Fix lint --- src/compiler/comments.ts | 2 +- src/compiler/core.ts | 2 +- src/compiler/declarationEmitter.ts | 2 +- src/compiler/factory.ts | 4 +- src/compiler/program.ts | 40 +++++++++------- src/compiler/scanner.ts | 2 +- src/compiler/transformer.ts | 2 +- src/compiler/tsc.ts | 6 +-- src/compiler/types.ts | 44 ++++++++--------- src/compiler/utilities.ts | 2 +- src/harness/fourslash.ts | 7 +-- src/harness/harness.ts | 10 ++-- src/harness/projectsRunner.ts | 47 ++++++++++--------- src/harness/unittests/moduleResolution.ts | 2 +- src/harness/unittests/programMissingFiles.ts | 10 ++-- .../unittests/reuseProgramStructure.ts | 6 +-- src/server/session.ts | 2 +- src/services/findAllReferences.ts | 28 +++++------ src/services/goToDefinition.ts | 2 +- src/services/importTracker.ts | 14 +++--- src/services/navigateTo.ts | 2 +- src/services/services.ts | 15 +++--- src/services/shims.ts | 4 +- src/services/types.ts | 2 +- 24 files changed, 133 insertions(+), 124 deletions(-) diff --git a/src/compiler/comments.ts b/src/compiler/comments.ts index adf15c7e28d..025a4f36d26 100644 --- a/src/compiler/comments.ts +++ b/src/compiler/comments.ts @@ -21,7 +21,7 @@ namespace ts { let declarationListContainerEnd = -1; let currentSourceFile: SourceFile; let currentText: string; - let currentLineMap: number[]; + let currentLineMap: ReadonlyArray; let detachedCommentsInfo: { nodePos: number, detachedCommentEndPos: number}[]; let hasWrittenComment = false; let disabled: boolean = printerOptions.removeComments; diff --git a/src/compiler/core.ts b/src/compiler/core.ts index 07824ab5d04..6eb17f1fb78 100644 --- a/src/compiler/core.ts +++ b/src/compiler/core.ts @@ -715,7 +715,7 @@ namespace ts { return result; } - export function sum, K extends string>(array: T[], prop: K): number { + export function sum, K extends string>(array: ReadonlyArray, prop: K): number { let result = 0; for (const v of array) { // Note: we need the following type assertion because of GH #17069 diff --git a/src/compiler/declarationEmitter.ts b/src/compiler/declarationEmitter.ts index 47a3be6efea..3de20915029 100644 --- a/src/compiler/declarationEmitter.ts +++ b/src/compiler/declarationEmitter.ts @@ -60,7 +60,7 @@ namespace ts { let enclosingDeclaration: Node; let resultHasExternalModuleIndicator: boolean; let currentText: string; - let currentLineMap: number[]; + let currentLineMap: ReadonlyArray; let currentIdentifiers: Map; let isCurrentFileExternalModule: boolean; let reportedDeclarationError = false; diff --git a/src/compiler/factory.ts b/src/compiler/factory.ts index 1d92c0f5315..9d7f6c82a7a 100644 --- a/src/compiler/factory.ts +++ b/src/compiler/factory.ts @@ -2339,13 +2339,13 @@ namespace ts { : node; } - export function createBundle(sourceFiles: SourceFile[]) { + export function createBundle(sourceFiles: ReadonlyArray) { const node = createNode(SyntaxKind.Bundle); node.sourceFiles = sourceFiles; return node; } - export function updateBundle(node: Bundle, sourceFiles: SourceFile[]) { + export function updateBundle(node: Bundle, sourceFiles: ReadonlyArray) { if (node.sourceFiles !== sourceFiles) { return createBundle(sourceFiles); } diff --git a/src/compiler/program.ts b/src/compiler/program.ts index 305058285f9..800e2f9e619 100644 --- a/src/compiler/program.ts +++ b/src/compiler/program.ts @@ -205,13 +205,15 @@ namespace ts { } export function getPreEmitDiagnostics(program: Program, sourceFile?: SourceFile, cancellationToken?: CancellationToken): Diagnostic[] { - let diagnostics = program.getOptionsDiagnostics(cancellationToken).concat( - program.getSyntacticDiagnostics(sourceFile, cancellationToken), - program.getGlobalDiagnostics(cancellationToken), - program.getSemanticDiagnostics(sourceFile, cancellationToken)); + const diagnostics = [ + ...program.getOptionsDiagnostics(cancellationToken), + ...program.getSyntacticDiagnostics(sourceFile, cancellationToken), + ...program.getGlobalDiagnostics(cancellationToken), + ...program.getSemanticDiagnostics(sourceFile, cancellationToken) + ]; if (program.getCompilerOptions().declaration) { - diagnostics = diagnostics.concat(program.getDeclarationDiagnostics(sourceFile, cancellationToken)); + addRange(diagnostics, program.getDeclarationDiagnostics(sourceFile, cancellationToken)); } return sortAndDeduplicateDiagnostics(diagnostics); @@ -223,7 +225,7 @@ namespace ts { getNewLine(): string; } - export function formatDiagnostics(diagnostics: Diagnostic[], host: FormatDiagnosticsHost): string { + export function formatDiagnostics(diagnostics: ReadonlyArray, host: FormatDiagnosticsHost): string { let output = ""; for (const diagnostic of diagnostics) { @@ -399,7 +401,7 @@ namespace ts { * @param oldProgram - Reuses an old program structure. * @returns A 'Program' object. */ - export function createProgram(rootNames: string[], options: CompilerOptions, host?: CompilerHost, oldProgram?: Program): Program { + export function createProgram(rootNames: ReadonlyArray, options: CompilerOptions, host?: CompilerHost, oldProgram?: Program): Program { let program: Program; let files: SourceFile[] = []; let commonSourceDirectory: string; @@ -996,7 +998,7 @@ namespace ts { } function emitWorker(program: Program, sourceFile: SourceFile, writeFileCallback: WriteFileCallback, cancellationToken: CancellationToken, emitOnlyDtsFiles?: boolean, customTransformers?: CustomTransformers): EmitResult { - let declarationDiagnostics: Diagnostic[] = []; + let declarationDiagnostics: ReadonlyArray = []; if (options.noEmit) { return { diagnostics: declarationDiagnostics, sourceMaps: undefined, emittedFiles: undefined, emitSkipped: true }; @@ -1006,10 +1008,12 @@ namespace ts { // immediately bail out. Note that we pass 'undefined' for 'sourceFile' so that we // get any preEmit diagnostics, not just the ones if (options.noEmitOnError) { - const diagnostics = program.getOptionsDiagnostics(cancellationToken).concat( - program.getSyntacticDiagnostics(sourceFile, cancellationToken), - program.getGlobalDiagnostics(cancellationToken), - program.getSemanticDiagnostics(sourceFile, cancellationToken)); + const diagnostics = [ + ...program.getOptionsDiagnostics(cancellationToken), + ...program.getSyntacticDiagnostics(sourceFile, cancellationToken), + ...program.getGlobalDiagnostics(cancellationToken), + ...program.getSemanticDiagnostics(sourceFile, cancellationToken) + ]; if (diagnostics.length === 0 && program.getCompilerOptions().declaration) { declarationDiagnostics = program.getDeclarationDiagnostics(/*sourceFile*/ undefined, cancellationToken); @@ -1060,8 +1064,8 @@ namespace ts { function getDiagnosticsHelper( sourceFile: SourceFile, - getDiagnostics: (sourceFile: SourceFile, cancellationToken: CancellationToken) => Diagnostic[], - cancellationToken: CancellationToken): Diagnostic[] { + getDiagnostics: (sourceFile: SourceFile, cancellationToken: CancellationToken) => ReadonlyArray, + cancellationToken: CancellationToken): ReadonlyArray { if (sourceFile) { return getDiagnostics(sourceFile, cancellationToken); } @@ -1073,15 +1077,15 @@ namespace ts { })); } - function getSyntacticDiagnostics(sourceFile: SourceFile, cancellationToken: CancellationToken): Diagnostic[] { + function getSyntacticDiagnostics(sourceFile: SourceFile, cancellationToken: CancellationToken): ReadonlyArray { return getDiagnosticsHelper(sourceFile, getSyntacticDiagnosticsForFile, cancellationToken); } - function getSemanticDiagnostics(sourceFile: SourceFile, cancellationToken: CancellationToken): Diagnostic[] { + function getSemanticDiagnostics(sourceFile: SourceFile, cancellationToken: CancellationToken): ReadonlyArray { return getDiagnosticsHelper(sourceFile, getSemanticDiagnosticsForFile, cancellationToken); } - function getDeclarationDiagnostics(sourceFile: SourceFile, cancellationToken: CancellationToken): Diagnostic[] { + function getDeclarationDiagnostics(sourceFile: SourceFile, cancellationToken: CancellationToken): ReadonlyArray { const options = program.getCompilerOptions(); // collect diagnostics from the program only once if either no source file was specified or out/outFile is set (bundled emit) if (!sourceFile || options.out || options.outFile) { @@ -1092,7 +1096,7 @@ namespace ts { } } - function getSyntacticDiagnosticsForFile(sourceFile: SourceFile): Diagnostic[] { + function getSyntacticDiagnosticsForFile(sourceFile: SourceFile): ReadonlyArray { // For JavaScript files, we report semantic errors for using TypeScript-only // constructs from within a JavaScript file as syntactic errors. if (isSourceFileJavaScript(sourceFile)) { diff --git a/src/compiler/scanner.ts b/src/compiler/scanner.ts index 09defaaf773..a130d8427da 100644 --- a/src/compiler/scanner.ts +++ b/src/compiler/scanner.ts @@ -343,7 +343,7 @@ namespace ts { } /* @internal */ - export function getLineStarts(sourceFile: SourceFileLike): number[] { + export function getLineStarts(sourceFile: SourceFileLike): ReadonlyArray { return sourceFile.lineMap || (sourceFile.lineMap = computeLineStarts(sourceFile.text)); } diff --git a/src/compiler/transformer.ts b/src/compiler/transformer.ts index d61195ad74b..341c0c9cd45 100644 --- a/src/compiler/transformer.ts +++ b/src/compiler/transformer.ts @@ -92,7 +92,7 @@ namespace ts { * @param transforms An array of `TransformerFactory` callbacks. * @param allowDtsFiles A value indicating whether to allow the transformation of .d.ts files. */ - export function transformNodes(resolver: EmitResolver, host: EmitHost, options: CompilerOptions, nodes: T[], transformers: TransformerFactory[], allowDtsFiles: boolean): TransformationResult { + export function transformNodes(resolver: EmitResolver, host: EmitHost, options: CompilerOptions, nodes: ReadonlyArray, transformers: ReadonlyArray>, allowDtsFiles: boolean): TransformationResult { const enabledSyntaxKindFeatures = new Array(SyntaxKind.Count); let lexicalEnvironmentVariableDeclarations: VariableDeclaration[]; let lexicalEnvironmentFunctionDeclarations: FunctionDeclaration[]; diff --git a/src/compiler/tsc.ts b/src/compiler/tsc.ts index db25afe45b6..d5e584c7ac5 100644 --- a/src/compiler/tsc.ts +++ b/src/compiler/tsc.ts @@ -469,7 +469,7 @@ namespace ts { let diagnostics: Diagnostic[]; // First get and report any syntactic errors. - diagnostics = program.getSyntacticDiagnostics(); + diagnostics = program.getSyntacticDiagnostics().slice(); // If we didn't have any syntactic errors, then also try getting the global and // semantic errors. @@ -477,13 +477,13 @@ namespace ts { diagnostics = program.getOptionsDiagnostics().concat(program.getGlobalDiagnostics()); if (diagnostics.length === 0) { - diagnostics = program.getSemanticDiagnostics(); + diagnostics = program.getSemanticDiagnostics().slice(); } } // Otherwise, emit and report any errors we ran into. const emitOutput = program.emit(); - diagnostics = diagnostics.concat(emitOutput.diagnostics); + addRange(diagnostics, emitOutput.diagnostics); reportDiagnostics(sortAndDeduplicateDiagnostics(diagnostics), compilerHost); diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 8fe602ea6f9..fe18839b904 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -2254,7 +2254,7 @@ namespace ts { */ export interface SourceFileLike { readonly text: string; - lineMap: number[]; + lineMap: ReadonlyArray; } @@ -2286,16 +2286,16 @@ namespace ts { */ /* @internal */ redirectInfo?: RedirectInfo | undefined; - amdDependencies: AmdDependency[]; + amdDependencies: ReadonlyArray; moduleName: string; - referencedFiles: FileReference[]; - typeReferenceDirectives: FileReference[]; + referencedFiles: ReadonlyArray; + typeReferenceDirectives: ReadonlyArray; languageVariant: LanguageVariant; isDeclarationFile: boolean; // this map is used by transpiler to supply alternative names for dependencies (i.e. in case of bundling) /* @internal */ - renamedDependencies?: Map; + renamedDependencies?: ReadonlyMap; /** * lib.d.ts should have a reference comment like @@ -2331,12 +2331,12 @@ namespace ts { /* @internal */ jsDocDiagnostics?: Diagnostic[]; // Stores additional file-level diagnostics reported by the program - /* @internal */ additionalSyntacticDiagnostics?: Diagnostic[]; + /* @internal */ additionalSyntacticDiagnostics?: ReadonlyArray; // Stores a line map for the file. // This field should never be used directly to obtain line map, use getLineMap function instead. - /* @internal */ lineMap: number[]; - /* @internal */ classifiableNames?: UnderscoreEscapedMap; + /* @internal */ lineMap: ReadonlyArray; + /* @internal */ classifiableNames?: ReadonlyUnderscoreEscapedMap; // Stores a mapping 'external module reference text' -> 'resolved file name' | undefined // It is used to resolve module names in the checker. // Content of this field should never be used directly - use getResolvedModuleFileName/setResolvedModuleFileName functions instead @@ -2351,7 +2351,7 @@ namespace ts { export interface Bundle extends Node { kind: SyntaxKind.Bundle; - sourceFiles: SourceFile[]; + sourceFiles: ReadonlyArray; } export interface JsonSourceFile extends SourceFile { @@ -2398,19 +2398,19 @@ namespace ts { /** * Get a list of root file names that were passed to a 'createProgram' */ - getRootFileNames(): string[]; + getRootFileNames(): ReadonlyArray; /** * Get a list of files in the program */ - getSourceFiles(): SourceFile[]; + getSourceFiles(): ReadonlyArray; /** * Get a list of file names that were passed to 'createProgram' or referenced in a * program source file but could not be located. */ /* @internal */ - getMissingFilePaths(): Path[]; + getMissingFilePaths(): ReadonlyArray; /** * Emits the JavaScript and declaration files. If targetSourceFile is not specified, then @@ -2424,11 +2424,11 @@ namespace ts { */ emit(targetSourceFile?: SourceFile, writeFile?: WriteFileCallback, cancellationToken?: CancellationToken, emitOnlyDtsFiles?: boolean, customTransformers?: CustomTransformers): EmitResult; - getOptionsDiagnostics(cancellationToken?: CancellationToken): Diagnostic[]; - getGlobalDiagnostics(cancellationToken?: CancellationToken): Diagnostic[]; - getSyntacticDiagnostics(sourceFile?: SourceFile, cancellationToken?: CancellationToken): Diagnostic[]; - getSemanticDiagnostics(sourceFile?: SourceFile, cancellationToken?: CancellationToken): Diagnostic[]; - getDeclarationDiagnostics(sourceFile?: SourceFile, cancellationToken?: CancellationToken): Diagnostic[]; + getOptionsDiagnostics(cancellationToken?: CancellationToken): ReadonlyArray; + getGlobalDiagnostics(cancellationToken?: CancellationToken): ReadonlyArray; + getSyntacticDiagnostics(sourceFile?: SourceFile, cancellationToken?: CancellationToken): ReadonlyArray; + getSemanticDiagnostics(sourceFile?: SourceFile, cancellationToken?: CancellationToken): ReadonlyArray; + getDeclarationDiagnostics(sourceFile?: SourceFile, cancellationToken?: CancellationToken): ReadonlyArray; /** * Gets a type checker that can be used to semantically analyze source files in the program. @@ -2522,7 +2522,7 @@ namespace ts { export interface EmitResult { emitSkipped: boolean; /** Contains declaration emit diagnostics */ - diagnostics: Diagnostic[]; + diagnostics: ReadonlyArray; emittedFiles: string[]; // Array of files the compiler wrote to disk /* @internal */ sourceMaps: SourceMapData[]; // Array of sourceMapData if compiler emitted sourcemaps } @@ -2531,9 +2531,9 @@ namespace ts { export interface TypeCheckerHost { getCompilerOptions(): CompilerOptions; - getSourceFiles(): SourceFile[]; + getSourceFiles(): ReadonlyArray; getSourceFile(fileName: string): SourceFile; - getResolvedTypeReferenceDirectives(): Map; + getResolvedTypeReferenceDirectives(): ReadonlyMap; } export interface TypeChecker { @@ -4137,7 +4137,7 @@ namespace ts { export interface SourceMapSource { fileName: string; text: string; - /* @internal */ lineMap: number[]; + /* @internal */ lineMap: ReadonlyArray; skipTrivia?: (pos: number) => number; } @@ -4244,7 +4244,7 @@ namespace ts { /* @internal */ export interface EmitHost extends ScriptReferenceHost { - getSourceFiles(): SourceFile[]; + getSourceFiles(): ReadonlyArray; /* @internal */ isSourceFileFromExternalLibrary(file: SourceFile): boolean; diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index f1a8dfd676b..d906ab4f6b1 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -2572,7 +2572,7 @@ namespace ts { * @param host An EmitHost. * @param targetSourceFile An optional target source file to emit. */ - export function getSourceFilesToEmit(host: EmitHost, targetSourceFile?: SourceFile): SourceFile[] { + export function getSourceFilesToEmit(host: EmitHost, targetSourceFile?: SourceFile): ReadonlyArray { const options = host.getCompilerOptions(); const isSourceFileFromExternalLibrary = (file: SourceFile) => host.isSourceFileFromExternalLibrary(file); if (options.outFile || options.out) { diff --git a/src/harness/fourslash.ts b/src/harness/fourslash.ts index 32c9fb67da0..ab33fa87997 100644 --- a/src/harness/fourslash.ts +++ b/src/harness/fourslash.ts @@ -490,7 +490,8 @@ namespace FourSlash { } private getDiagnostics(fileName: string): ts.Diagnostic[] { - return this.languageService.getSyntacticDiagnostics(fileName).concat(this.languageService.getSemanticDiagnostics(fileName)); + return ts.concatenate(this.languageService.getSyntacticDiagnostics(fileName), + this.languageService.getSemanticDiagnostics(fileName)); } private getAllDiagnostics(): ts.Diagnostic[] { @@ -1148,7 +1149,7 @@ namespace FourSlash { this.testDiagnostics(expected, diagnostics); } - private testDiagnostics(expected: string, diagnostics: ts.Diagnostic[]) { + private testDiagnostics(expected: string, diagnostics: ReadonlyArray) { const realized = ts.realizeDiagnostics(diagnostics, "\r\n"); const actual = stringify(realized); assert.equal(actual, expected); @@ -1585,7 +1586,7 @@ namespace FourSlash { public printErrorList() { const syntacticErrors = this.languageService.getSyntacticDiagnostics(this.activeFile.fileName); const semanticErrors = this.languageService.getSemanticDiagnostics(this.activeFile.fileName); - const errorList = syntacticErrors.concat(semanticErrors); + const errorList = ts.concatenate(syntacticErrors, semanticErrors); Harness.IO.log(`Error list (${errorList.length} errors)`); if (errorList.length) { diff --git a/src/harness/harness.ts b/src/harness/harness.ts index bf5cde2bc48..9443844cf9a 100644 --- a/src/harness/harness.ts +++ b/src/harness/harness.ts @@ -207,7 +207,7 @@ namespace Utils { return a !== undefined && typeof a.pos === "number"; } - export function convertDiagnostics(diagnostics: ts.Diagnostic[]) { + export function convertDiagnostics(diagnostics: ReadonlyArray) { return diagnostics.map(convertDiagnostic); } @@ -337,7 +337,7 @@ namespace Utils { } } - export function assertDiagnosticsEquals(array1: ts.Diagnostic[], array2: ts.Diagnostic[]) { + export function assertDiagnosticsEquals(array1: ReadonlyArray, array2: ReadonlyArray) { if (array1 === array2) { return; } @@ -1284,12 +1284,12 @@ namespace Harness { return normalized; } - export function minimalDiagnosticsToString(diagnostics: ts.Diagnostic[]) { + export function minimalDiagnosticsToString(diagnostics: ReadonlyArray) { return ts.formatDiagnostics(diagnostics, { getCanonicalFileName, getCurrentDirectory: () => "", getNewLine: () => Harness.IO.newLine() }); } - export function getErrorBaseline(inputFiles: TestFile[], diagnostics: ts.Diagnostic[]) { - diagnostics.sort(ts.compareDiagnostics); + export function getErrorBaseline(inputFiles: ReadonlyArray, diagnostics: ReadonlyArray) { + diagnostics = diagnostics.slice().sort(ts.compareDiagnostics); let outputLines = ""; // Count up all errors that were found in files other than lib.d.ts so we don't miss any let totalErrorsReportedInNonLibraryFiles = 0; diff --git a/src/harness/projectsRunner.ts b/src/harness/projectsRunner.ts index 752767d9706..6c13261d757 100644 --- a/src/harness/projectsRunner.ts +++ b/src/harness/projectsRunner.ts @@ -5,7 +5,7 @@ interface ProjectRunnerTestCase { scenario: string; projectRoot: string; // project where it lives - this also is the current directory when compiling - inputFiles: string[]; // list of input files to be given to program + inputFiles: ReadonlyArray; // list of input files to be given to program resolveMapRoot?: boolean; // should we resolve this map root and give compiler the absolute disk path as map root? resolveSourceRoot?: boolean; // should we resolve this source root and give compiler the absolute disk path as map root? baselineCheck?: boolean; // Verify the baselines of output files, if this is false, we will write to output to the disk but there is no verification of baselines @@ -15,8 +15,8 @@ interface ProjectRunnerTestCase { interface ProjectRunnerTestCaseResolutionInfo extends ProjectRunnerTestCase { // Apart from actual test case the results of the resolution - resolvedInputFiles: string[]; // List of files that were asked to read by compiler - emittedFiles: string[]; // List of files that were emitted by the compiler + resolvedInputFiles: ReadonlyArray; // List of files that were asked to read by compiler + emittedFiles: ReadonlyArray; // List of files that were emitted by the compiler } interface BatchCompileProjectTestCaseEmittedFile extends Harness.Compiler.GeneratedFile { @@ -24,12 +24,12 @@ interface BatchCompileProjectTestCaseEmittedFile extends Harness.Compiler.Genera } interface CompileProjectFilesResult { - configFileSourceFiles: ts.SourceFile[]; + configFileSourceFiles: ReadonlyArray; moduleKind: ts.ModuleKind; program?: ts.Program; compilerOptions?: ts.CompilerOptions; - errors: ts.Diagnostic[]; - sourceMapData?: ts.SourceMapData[]; + errors: ReadonlyArray; + sourceMapData?: ReadonlyArray; } interface BatchCompileProjectTestCaseResult extends CompileProjectFilesResult { @@ -125,17 +125,17 @@ class ProjectRunner extends RunnerBase { return Harness.IO.resolvePath(testCase.projectRoot); } - function compileProjectFiles(moduleKind: ts.ModuleKind, configFileSourceFiles: ts.SourceFile[], - getInputFiles: () => string[], + function compileProjectFiles(moduleKind: ts.ModuleKind, configFileSourceFiles: ReadonlyArray, + getInputFiles: () => ReadonlyArray, getSourceFileTextImpl: (fileName: string) => string, writeFile: (fileName: string, data: string, writeByteOrderMark: boolean) => void, compilerOptions: ts.CompilerOptions): CompileProjectFilesResult { const program = ts.createProgram(getInputFiles(), compilerOptions, createCompilerHost()); - let errors = ts.getPreEmitDiagnostics(program); + const errors = ts.getPreEmitDiagnostics(program); const emitResult = program.emit(); - errors = ts.concatenate(errors, emitResult.diagnostics); + ts.addRange(errors, emitResult.diagnostics); const sourceMapData = emitResult.sourceMaps; // Clean up source map data that will be used in baselining @@ -235,7 +235,7 @@ class ProjectRunner extends RunnerBase { compilerOptions, sourceMapData: projectCompilerResult.sourceMapData, outputFiles, - errors: errors ? errors.concat(projectCompilerResult.errors) : projectCompilerResult.errors, + errors: errors ? ts.concatenate(errors, projectCompilerResult.errors) : projectCompilerResult.errors, }; function createCompilerOptions() { @@ -422,16 +422,21 @@ class ProjectRunner extends RunnerBase { } function getErrorsBaseline(compilerResult: CompileProjectFilesResult) { - const inputFiles = ts.map(compilerResult.configFileSourceFiles.concat( - compilerResult.program ? - ts.filter(compilerResult.program.getSourceFiles(), sourceFile => !Harness.isDefaultLibraryFile(sourceFile.fileName)) : - []), - (sourceFile): Harness.Compiler.TestFile => ({ - unitName: ts.isRootedDiskPath(sourceFile.fileName) ? - RunnerBase.removeFullPaths(sourceFile.fileName) : - sourceFile.fileName, - content: sourceFile.text - })); + const inputSourceFiles = compilerResult.configFileSourceFiles.slice(); + if (compilerResult.program) { + for (const sourceFile of compilerResult.program.getSourceFiles()) { + if (!Harness.isDefaultLibraryFile(sourceFile.fileName)) { + inputSourceFiles.push(sourceFile); + } + } + } + + const inputFiles = inputSourceFiles.map(sourceFile => ({ + unitName: ts.isRootedDiskPath(sourceFile.fileName) ? + RunnerBase.removeFullPaths(sourceFile.fileName) : + sourceFile.fileName, + content: sourceFile.text + })); return Harness.Compiler.getErrorBaseline(inputFiles, compilerResult.errors); } diff --git a/src/harness/unittests/moduleResolution.ts b/src/harness/unittests/moduleResolution.ts index 79aafb4986d..ed8dcf0c7b4 100644 --- a/src/harness/unittests/moduleResolution.ts +++ b/src/harness/unittests/moduleResolution.ts @@ -425,7 +425,7 @@ export = C; readFile: notImplemented }; const program = createProgram(rootFiles, options, host); - const diagnostics = sortAndDeduplicateDiagnostics(program.getSemanticDiagnostics().concat(program.getOptionsDiagnostics())); + const diagnostics = sortAndDeduplicateDiagnostics([...program.getSemanticDiagnostics(), ...program.getOptionsDiagnostics()]); assert.equal(diagnostics.length, diagnosticCodes.length, `Incorrect number of expected diagnostics, expected ${diagnosticCodes.length}, got '${Harness.Compiler.minimalDiagnosticsToString(diagnostics)}'`); for (let i = 0; i < diagnosticCodes.length; i++) { assert.equal(diagnostics[i].code, diagnosticCodes[i], `Expected diagnostic code ${diagnosticCodes[i]}, got '${diagnostics[i].code}': '${diagnostics[i].messageText}'`); diff --git a/src/harness/unittests/programMissingFiles.ts b/src/harness/unittests/programMissingFiles.ts index 668b684a250..ea644599de1 100644 --- a/src/harness/unittests/programMissingFiles.ts +++ b/src/harness/unittests/programMissingFiles.ts @@ -48,18 +48,18 @@ namespace ts { const program = createProgram(["./nonexistent.ts"], options, testCompilerHost); const missing = program.getMissingFilePaths(); assert.isDefined(missing); - assert.deepEqual(missing, ["d:/pretend/nonexistent.ts"]); // Absolute path + assert.deepEqual(missing, ["d:/pretend/nonexistent.ts" as Path]); // Absolute path }); it("handles multiple missing root files", () => { const program = createProgram(["./nonexistent0.ts", "./nonexistent1.ts"], options, testCompilerHost); - const missing = program.getMissingFilePaths().sort(); + const missing = program.getMissingFilePaths().slice().sort(); assert.deepEqual(missing, ["d:/pretend/nonexistent0.ts", "d:/pretend/nonexistent1.ts"]); }); it("handles a mix of present and missing root files", () => { const program = createProgram(["./nonexistent0.ts", emptyFileRelativePath, "./nonexistent1.ts"], options, testCompilerHost); - const missing = program.getMissingFilePaths().sort(); + const missing = program.getMissingFilePaths().slice().sort(); assert.deepEqual(missing, ["d:/pretend/nonexistent0.ts", "d:/pretend/nonexistent1.ts"]); }); @@ -67,7 +67,7 @@ namespace ts { const program = createProgram(["./nonexistent.ts", "./nonexistent.ts"], options, testCompilerHost); const missing = program.getMissingFilePaths(); assert.isDefined(missing); - assert.deepEqual(missing, ["d:/pretend/nonexistent.ts"]); + assert.deepEqual(missing, ["d:/pretend/nonexistent.ts" as Path]); }); it("normalizes file paths", () => { @@ -81,7 +81,7 @@ namespace ts { it("handles missing triple slash references", () => { const program = createProgram([referenceFileRelativePath], options, testCompilerHost); - const missing = program.getMissingFilePaths().sort(); + const missing = program.getMissingFilePaths().slice().sort(); assert.isDefined(missing); assert.deepEqual(missing, [ // From absolute reference diff --git a/src/harness/unittests/reuseProgramStructure.ts b/src/harness/unittests/reuseProgramStructure.ts index 5062f4c4b39..0c2a4a7052b 100644 --- a/src/harness/unittests/reuseProgramStructure.ts +++ b/src/harness/unittests/reuseProgramStructure.ts @@ -21,7 +21,7 @@ namespace ts { } interface ProgramWithSourceTexts extends Program { - sourceTexts?: NamedSourceText[]; + sourceTexts?: ReadonlyArray; host: TestCompilerHost; } @@ -106,7 +106,7 @@ namespace ts { return file; } - function createTestCompilerHost(texts: NamedSourceText[], target: ScriptTarget, oldProgram?: ProgramWithSourceTexts): TestCompilerHost { + function createTestCompilerHost(texts: ReadonlyArray, target: ScriptTarget, oldProgram?: ProgramWithSourceTexts): TestCompilerHost { const files = arrayToMap(texts, t => t.name, t => { if (oldProgram) { let oldFile = oldProgram.getSourceFile(t.name); @@ -162,7 +162,7 @@ namespace ts { return program; } - function updateProgram(oldProgram: ProgramWithSourceTexts, rootNames: string[], options: CompilerOptions, updater: (files: NamedSourceText[]) => void, newTexts?: NamedSourceText[]) { + function updateProgram(oldProgram: ProgramWithSourceTexts, rootNames: ReadonlyArray, options: CompilerOptions, updater: (files: NamedSourceText[]) => void, newTexts?: NamedSourceText[]) { if (!newTexts) { newTexts = (oldProgram).sourceTexts.slice(0); } diff --git a/src/server/session.ts b/src/server/session.ts index c5b69576811..0b3038a408d 100644 --- a/src/server/session.ts +++ b/src/server/session.ts @@ -425,7 +425,7 @@ namespace ts.server { private semanticCheck(file: NormalizedPath, project: Project) { try { - let diags: Diagnostic[] = []; + let diags: ReadonlyArray = emptyArray; if (!isDeclarationFileInJSOnlyNonConfiguredProject(project, file)) { diags = project.getLanguageService().getSemanticDiagnostics(file); } diff --git a/src/services/findAllReferences.ts b/src/services/findAllReferences.ts index ad26dae2115..4c8563b1b94 100644 --- a/src/services/findAllReferences.ts +++ b/src/services/findAllReferences.ts @@ -41,7 +41,7 @@ namespace ts.FindAllReferences { readonly implementations?: boolean; } - export function findReferencedSymbols(program: Program, cancellationToken: CancellationToken, sourceFiles: SourceFile[], sourceFile: SourceFile, position: number): ReferencedSymbol[] | undefined { + export function findReferencedSymbols(program: Program, cancellationToken: CancellationToken, sourceFiles: ReadonlyArray, sourceFile: SourceFile, position: number): ReferencedSymbol[] | undefined { const referencedSymbols = findAllReferencedSymbols(program, cancellationToken, sourceFiles, sourceFile, position); if (!referencedSymbols || !referencedSymbols.length) { @@ -60,7 +60,7 @@ namespace ts.FindAllReferences { return out; } - export function getImplementationsAtPosition(program: Program, cancellationToken: CancellationToken, sourceFiles: SourceFile[], sourceFile: SourceFile, position: number): ImplementationLocation[] { + export function getImplementationsAtPosition(program: Program, cancellationToken: CancellationToken, sourceFiles: ReadonlyArray, sourceFile: SourceFile, position: number): ImplementationLocation[] { // A node in a JSDoc comment can't have an implementation anyway. const node = getTouchingPropertyName(sourceFile, position, /*includeJsDocComment*/ false); const referenceEntries = getImplementationReferenceEntries(program, cancellationToken, sourceFiles, node); @@ -68,7 +68,7 @@ namespace ts.FindAllReferences { return map(referenceEntries, entry => toImplementationLocation(entry, checker)); } - function getImplementationReferenceEntries(program: Program, cancellationToken: CancellationToken, sourceFiles: SourceFile[], node: Node): Entry[] | undefined { + function getImplementationReferenceEntries(program: Program, cancellationToken: CancellationToken, sourceFiles: ReadonlyArray, node: Node): Entry[] | undefined { if (node.kind === SyntaxKind.SourceFile) { return undefined; } @@ -93,16 +93,16 @@ namespace ts.FindAllReferences { } } - export function findReferencedEntries(program: Program, cancellationToken: CancellationToken, sourceFiles: SourceFile[], sourceFile: SourceFile, position: number, options?: Options): ReferenceEntry[] | undefined { + export function findReferencedEntries(program: Program, cancellationToken: CancellationToken, sourceFiles: ReadonlyArray, sourceFile: SourceFile, position: number, options?: Options): ReferenceEntry[] | undefined { const x = flattenEntries(findAllReferencedSymbols(program, cancellationToken, sourceFiles, sourceFile, position, options)); return map(x, toReferenceEntry); } - export function getReferenceEntriesForNode(node: Node, program: Program, sourceFiles: SourceFile[], cancellationToken: CancellationToken, options: Options = {}): Entry[] | undefined { + export function getReferenceEntriesForNode(node: Node, program: Program, sourceFiles: ReadonlyArray, cancellationToken: CancellationToken, options: Options = {}): Entry[] | undefined { return flattenEntries(Core.getReferencedSymbolsForNode(node, program, sourceFiles, cancellationToken, options)); } - function findAllReferencedSymbols(program: Program, cancellationToken: CancellationToken, sourceFiles: SourceFile[], sourceFile: SourceFile, position: number, options?: Options): SymbolAndEntries[] | undefined { + function findAllReferencedSymbols(program: Program, cancellationToken: CancellationToken, sourceFiles: ReadonlyArray, sourceFile: SourceFile, position: number, options?: Options): SymbolAndEntries[] | undefined { const node = getTouchingPropertyName(sourceFile, position, /*includeJsDocComment*/ true); return Core.getReferencedSymbolsForNode(node, program, sourceFiles, cancellationToken, options); } @@ -264,7 +264,7 @@ namespace ts.FindAllReferences { /* @internal */ namespace ts.FindAllReferences.Core { /** Core find-all-references algorithm. Handles special cases before delegating to `getReferencedSymbolsForSymbol`. */ - export function getReferencedSymbolsForNode(node: Node, program: Program, sourceFiles: SourceFile[], cancellationToken: CancellationToken, options: Options = {}): SymbolAndEntries[] | undefined { + export function getReferencedSymbolsForNode(node: Node, program: Program, sourceFiles: ReadonlyArray, cancellationToken: CancellationToken, options: Options = {}): SymbolAndEntries[] | undefined { if (node.kind === ts.SyntaxKind.SourceFile) { return undefined; } @@ -313,7 +313,7 @@ namespace ts.FindAllReferences.Core { } } - function getReferencedSymbolsForModule(program: Program, symbol: Symbol, sourceFiles: SourceFile[]): SymbolAndEntries[] { + function getReferencedSymbolsForModule(program: Program, symbol: Symbol, sourceFiles: ReadonlyArray): SymbolAndEntries[] { Debug.assert(!!symbol.valueDeclaration); const references = findModuleReferences(program, sourceFiles, symbol).map(reference => { @@ -349,7 +349,7 @@ namespace ts.FindAllReferences.Core { } /** getReferencedSymbols for special node kinds. */ - function getReferencedSymbolsSpecial(node: Node, sourceFiles: SourceFile[], cancellationToken: CancellationToken): SymbolAndEntries[] | undefined { + function getReferencedSymbolsSpecial(node: Node, sourceFiles: ReadonlyArray, cancellationToken: CancellationToken): SymbolAndEntries[] | undefined { if (isTypeKeyword(node.kind)) { return getAllReferencesForKeyword(sourceFiles, node.kind, cancellationToken); } @@ -380,7 +380,7 @@ namespace ts.FindAllReferences.Core { } /** Core find-all-references algorithm for a normal symbol. */ - function getReferencedSymbolsForSymbol(symbol: Symbol, node: Node, sourceFiles: SourceFile[], checker: TypeChecker, cancellationToken: CancellationToken, options: Options): SymbolAndEntries[] { + function getReferencedSymbolsForSymbol(symbol: Symbol, node: Node, sourceFiles: ReadonlyArray, checker: TypeChecker, cancellationToken: CancellationToken, options: Options): SymbolAndEntries[] { symbol = skipPastExportOrImportSpecifier(symbol, node, checker); // Compute the meaning from the location and the symbol it references @@ -474,7 +474,7 @@ namespace ts.FindAllReferences.Core { readonly markSeenReExportRHS = nodeSeenTracker(); constructor( - readonly sourceFiles: SourceFile[], + readonly sourceFiles: ReadonlyArray, /** True if we're searching for constructor references. */ readonly isForConstructor: boolean, readonly checker: TypeChecker, @@ -759,7 +759,7 @@ namespace ts.FindAllReferences.Core { } } - function getAllReferencesForKeyword(sourceFiles: SourceFile[], keywordKind: ts.SyntaxKind, cancellationToken: CancellationToken): SymbolAndEntries[] { + function getAllReferencesForKeyword(sourceFiles: ReadonlyArray, keywordKind: ts.SyntaxKind, cancellationToken: CancellationToken): SymbolAndEntries[] { const references: NodeEntry[] = []; for (const sourceFile of sourceFiles) { cancellationToken.throwIfCancellationRequested(); @@ -1259,7 +1259,7 @@ namespace ts.FindAllReferences.Core { return [{ definition: { type: "symbol", symbol: searchSpaceNode.symbol, node: superKeyword }, references }]; } - function getReferencesForThisKeyword(thisOrSuperKeyword: Node, sourceFiles: SourceFile[], cancellationToken: CancellationToken): SymbolAndEntries[] { + function getReferencesForThisKeyword(thisOrSuperKeyword: Node, sourceFiles: ReadonlyArray, cancellationToken: CancellationToken): SymbolAndEntries[] { let searchSpaceNode = getThisContainer(thisOrSuperKeyword, /* includeArrowFunctions */ false); // Whether 'this' occurs in a static context within a class. @@ -1355,7 +1355,7 @@ namespace ts.FindAllReferences.Core { } } - function getReferencesForStringLiteral(node: StringLiteral, sourceFiles: SourceFile[], cancellationToken: CancellationToken): SymbolAndEntries[] { + function getReferencesForStringLiteral(node: StringLiteral, sourceFiles: ReadonlyArray, cancellationToken: CancellationToken): SymbolAndEntries[] { const references: NodeEntry[] = []; for (const sourceFile of sourceFiles) { diff --git a/src/services/goToDefinition.ts b/src/services/goToDefinition.ts index f60349e4ea5..cb2be7d484c 100644 --- a/src/services/goToDefinition.ts +++ b/src/services/goToDefinition.ts @@ -279,7 +279,7 @@ namespace ts.GoToDefinition { return createDefinitionInfo(decl, symbolKind, symbolName, containerName); } - function findReferenceInPosition(refs: FileReference[], pos: number): FileReference { + function findReferenceInPosition(refs: ReadonlyArray, pos: number): FileReference { for (const ref of refs) { if (ref.pos <= pos && pos <= ref.end) { return ref; diff --git a/src/services/importTracker.ts b/src/services/importTracker.ts index 1bbf175c7fe..d4301db2f83 100644 --- a/src/services/importTracker.ts +++ b/src/services/importTracker.ts @@ -7,12 +7,12 @@ namespace ts.FindAllReferences { /** For rename imports/exports `{ foo as bar }`, `foo` is not a local, so it may be added as a reference immediately without further searching. */ singleReferences: Identifier[]; /** List of source files that may (or may not) use the symbol via a namespace. (For UMD modules this is every file.) */ - indirectUsers: SourceFile[]; + indirectUsers: ReadonlyArray; } export type ImportTracker = (exportSymbol: Symbol, exportInfo: ExportInfo, isForRename: boolean) => ImportsResult; /** Creates the imports map and returns an ImportTracker that uses it. Call this lazily to avoid calling `getDirectImportsMap` unnecessarily. */ - export function createImportTracker(sourceFiles: SourceFile[], checker: TypeChecker, cancellationToken: CancellationToken): ImportTracker { + export function createImportTracker(sourceFiles: ReadonlyArray, checker: TypeChecker, cancellationToken: CancellationToken): ImportTracker { const allDirectImports = getDirectImportsMap(sourceFiles, checker, cancellationToken); return (exportSymbol, exportInfo, isForRename) => { const { directImports, indirectUsers } = getImportersForExport(sourceFiles, allDirectImports, exportInfo, checker, cancellationToken); @@ -38,12 +38,12 @@ namespace ts.FindAllReferences { /** Returns import statements that directly reference the exporting module, and a list of files that may access the module through a namespace. */ function getImportersForExport( - sourceFiles: SourceFile[], + sourceFiles: ReadonlyArray, allDirectImports: Map, { exportingModuleSymbol, exportKind }: ExportInfo, checker: TypeChecker, cancellationToken: CancellationToken - ): { directImports: Importer[], indirectUsers: SourceFile[] } { + ): { directImports: Importer[], indirectUsers: ReadonlyArray } { const markSeenDirectImport = nodeSeenTracker(); const markSeenIndirectUser = nodeSeenTracker(); const directImports: Importer[] = []; @@ -54,7 +54,7 @@ namespace ts.FindAllReferences { return { directImports, indirectUsers: getIndirectUsers() }; - function getIndirectUsers(): SourceFile[] { + function getIndirectUsers(): ReadonlyArray { if (isAvailableThroughGlobal) { // It has `export as namespace`, so anything could potentially use it. return sourceFiles; @@ -313,7 +313,7 @@ namespace ts.FindAllReferences { | { kind: "import", literal: StringLiteral } /** or */ | { kind: "reference", referencingFile: SourceFile, ref: FileReference }; - export function findModuleReferences(program: Program, sourceFiles: SourceFile[], searchModuleSymbol: Symbol): ModuleReference[] { + export function findModuleReferences(program: Program, sourceFiles: ReadonlyArray, searchModuleSymbol: Symbol): ModuleReference[] { const refs: ModuleReference[] = []; const checker = program.getTypeChecker(); for (const referencingFile of sourceFiles) { @@ -343,7 +343,7 @@ namespace ts.FindAllReferences { } /** Returns a map from a module symbol Id to all import statements that directly reference the module. */ - function getDirectImportsMap(sourceFiles: SourceFile[], checker: TypeChecker, cancellationToken: CancellationToken): Map { + function getDirectImportsMap(sourceFiles: ReadonlyArray, checker: TypeChecker, cancellationToken: CancellationToken): Map { const map = createMap(); for (const sourceFile of sourceFiles) { diff --git a/src/services/navigateTo.ts b/src/services/navigateTo.ts index 122ada09019..6d84769082d 100644 --- a/src/services/navigateTo.ts +++ b/src/services/navigateTo.ts @@ -2,7 +2,7 @@ namespace ts.NavigateTo { type RawNavigateToItem = { name: string; fileName: string; matchKind: PatternMatchKind; isCaseSensitive: boolean; declaration: Declaration }; - export function getNavigateToItems(sourceFiles: SourceFile[], checker: TypeChecker, cancellationToken: CancellationToken, searchValue: string, maxResultCount: number, excludeDtsFiles: boolean): NavigateToItem[] { + export function getNavigateToItems(sourceFiles: ReadonlyArray, checker: TypeChecker, cancellationToken: CancellationToken, searchValue: string, maxResultCount: number, excludeDtsFiles: boolean): NavigateToItem[] { const patternMatcher = createPatternMatcher(searchValue); let rawItems: RawNavigateToItem[] = []; diff --git a/src/services/services.ts b/src/services/services.ts index 7354e706cc9..bada7ff021d 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -495,7 +495,7 @@ namespace ts { public path: Path; public text: string; public scriptSnapshot: IScriptSnapshot; - public lineMap: number[]; + public lineMap: ReadonlyArray; public statements: NodeArray; public endOfFileToken: Token; @@ -545,7 +545,7 @@ namespace ts { return ts.getLineAndCharacterOfPosition(this, position); } - public getLineStarts(): number[] { + public getLineStarts(): ReadonlyArray { return getLineStarts(this); } @@ -1336,10 +1336,10 @@ namespace ts { } /// Diagnostics - function getSyntacticDiagnostics(fileName: string) { + function getSyntacticDiagnostics(fileName: string): Diagnostic[] { synchronizeHostData(); - return program.getSyntacticDiagnostics(getValidSourceFile(fileName), cancellationToken); + return program.getSyntacticDiagnostics(getValidSourceFile(fileName), cancellationToken).slice(); } /** @@ -1356,18 +1356,17 @@ namespace ts { const semanticDiagnostics = program.getSemanticDiagnostics(targetSourceFile, cancellationToken); if (!program.getCompilerOptions().declaration) { - return semanticDiagnostics; + return semanticDiagnostics.slice(); } // If '-d' is enabled, check for emitter error. One example of emitter error is export class implements non-export interface const declarationDiagnostics = program.getDeclarationDiagnostics(targetSourceFile, cancellationToken); - return concatenate(semanticDiagnostics, declarationDiagnostics); + return [...semanticDiagnostics, ...declarationDiagnostics]; } function getCompilerOptionsDiagnostics() { synchronizeHostData(); - return program.getOptionsDiagnostics(cancellationToken).concat( - program.getGlobalDiagnostics(cancellationToken)); + return [...program.getOptionsDiagnostics(cancellationToken), ...program.getGlobalDiagnostics(cancellationToken)]; } function getCompletionsAtPosition(fileName: string, position: number): CompletionInfo { diff --git a/src/services/shims.ts b/src/services/shims.ts index 40d4ff2e935..9851d8e6c89 100644 --- a/src/services/shims.ts +++ b/src/services/shims.ts @@ -568,7 +568,7 @@ namespace ts { } } - export function realizeDiagnostics(diagnostics: Diagnostic[], newLine: string): { message: string; start: number; length: number; category: string; code: number; }[] { + export function realizeDiagnostics(diagnostics: ReadonlyArray, newLine: string): { message: string; start: number; length: number; category: string; code: number; }[] { return diagnostics.map(d => realizeDiagnostic(d, newLine)); } @@ -641,7 +641,7 @@ namespace ts { }); } - private realizeDiagnostics(diagnostics: Diagnostic[]): { message: string; start: number; length: number; category: string; }[] { + private realizeDiagnostics(diagnostics: ReadonlyArray): { message: string; start: number; length: number; category: string; }[] { const newLine = getNewLineOrDefaultFromHost(this.host); return ts.realizeDiagnostics(diagnostics, newLine); } diff --git a/src/services/types.ts b/src/services/types.ts index f4f323b8e9f..609eaf11de5 100644 --- a/src/services/types.ts +++ b/src/services/types.ts @@ -68,7 +68,7 @@ namespace ts { getLineAndCharacterOfPosition(pos: number): LineAndCharacter; getLineEndOfPosition(pos: number): number; - getLineStarts(): number[]; + getLineStarts(): ReadonlyArray; getPositionOfLineAndCharacter(line: number, character: number): number; update(newText: string, textChangeRange: TextChangeRange): SourceFile; } From c4ed55459773092597d0dc1cb500bf176daa62d0 Mon Sep 17 00:00:00 2001 From: Andy Date: Thu, 24 Aug 2017 10:27:07 -0700 Subject: [PATCH 07/26] Simplify `isExpression` check (#17741) --- src/compiler/checker.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 4a07f349c9a..24a9a868c4a 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -15220,7 +15220,7 @@ namespace ts { // example, given a 'function wrap(cb: (x: T) => U): (x: T) => U' and a call expression // 'let f: (x: string) => number = wrap(s => s.length)', we infer from the declared type of 'f' to the // return type of 'wrap'. - if (isExpression(node)) { + if (node.kind !== SyntaxKind.Decorator) { const contextualType = getContextualType(node); if (contextualType) { // We clone the contextual mapper to avoid disturbing a resolution in progress for an From 610104bef8ab3493daa0595d099321556aad7b0d Mon Sep 17 00:00:00 2001 From: Ron Buckton Date: Thu, 24 Aug 2017 13:40:20 -0700 Subject: [PATCH 08/26] Switch to arrow for ts class wrapper IIFE --- src/compiler/factory.ts | 43 +++++++++++++- src/compiler/transformers/es2015.ts | 59 ++----------------- src/compiler/transformers/ts.ts | 5 +- src/compiler/types.ts | 1 + src/compiler/utilities.ts | 6 +- .../reference/decoratorOnClassMethod11.js | 1 + .../reference/decoratorOnClassMethod12.js | 1 + .../inferringClassMembersFromAssignments.js | 1 + tests/baselines/reference/superAccess2.js | 1 + ...thisInArrowFunctionInStaticInitializer1.js | 1 + .../reference/thisInConstructorParameter2.js | 1 + .../reference/thisInInvalidContexts.js | 1 + .../thisInInvalidContextsExternalModule.js | 1 + .../reference/thisInOuterClassBody.js | 1 + .../reference/typeOfThisInStaticMembers2.js | 1 + 15 files changed, 64 insertions(+), 60 deletions(-) diff --git a/src/compiler/factory.ts b/src/compiler/factory.ts index 9d7f6c82a7a..4492c3ed474 100644 --- a/src/compiler/factory.ts +++ b/src/compiler/factory.ts @@ -2372,6 +2372,24 @@ namespace ts { ); } + export function createImmediatelyInvokedArrowFunction(statements: Statement[]): CallExpression; + export function createImmediatelyInvokedArrowFunction(statements: Statement[], param: ParameterDeclaration, paramValue: Expression): CallExpression; + export function createImmediatelyInvokedArrowFunction(statements: Statement[], param?: ParameterDeclaration, paramValue?: Expression) { + return createCall( + createArrowFunction( + /*modifiers*/ undefined, + /*typeParameters*/ undefined, + /*parameters*/ param ? [param] : [], + /*type*/ undefined, + /*equalsGreaterThanToken*/ undefined, + createBlock(statements, /*multiLine*/ true) + ), + /*typeArguments*/ undefined, + /*argumentsArray*/ paramValue ? [paramValue] : [] + ); + } + + export function createComma(left: Expression, right: Expression) { return createBinary(left, SyntaxKind.CommaToken, right); } @@ -4036,8 +4054,31 @@ namespace ts { } } + /** + * Determines whether a node is a parenthesized expression that can be ignored when recreating outer expressions. + * + * A parenthesized expression can be ignored when all of the following are true: + * + * - It's `pos` and `end` are not -1 + * - It does not have a custom source map range + * - It does not have a custom comment range + * - It does not have synthetic leading or trailing comments + * + * If an outermost parenthesized expression is ignored, but the containing expression requires a parentheses around + * the expression to maintain precedence, a new parenthesized expression should be created automatically when + * the containing expression is created/updated. + */ + function isIgnorableParen(node: Expression) { + return node.kind === SyntaxKind.ParenthesizedExpression + && nodeIsSynthesized(node) + && nodeIsSynthesized(getSourceMapRange(node)) + && nodeIsSynthesized(getCommentRange(node)) + && !some(getSyntheticLeadingComments(node)) + && !some(getSyntheticTrailingComments(node)); + } + export function recreateOuterExpressions(outerExpression: Expression | undefined, innerExpression: Expression, kinds = OuterExpressionKinds.All): Expression { - if (outerExpression && isOuterExpression(outerExpression, kinds)) { + if (outerExpression && isOuterExpression(outerExpression, kinds) && !isIgnorableParen(outerExpression)) { return updateOuterExpression( outerExpression, recreateOuterExpressions(outerExpression.expression, innerExpression) diff --git a/src/compiler/transformers/es2015.ts b/src/compiler/transformers/es2015.ts index ce907d49bee..e9c84be5176 100644 --- a/src/compiler/transformers/es2015.ts +++ b/src/compiler/transformers/es2015.ts @@ -339,64 +339,12 @@ namespace ts { && !(node).expression; } - function isClassLikeVariableStatement(node: Node) { - if (!isVariableStatement(node)) return false; - const variable = singleOrUndefined((node).declarationList.declarations); - return variable - && variable.initializer - && isIdentifier(variable.name) - && (isClassLike(variable.initializer) - || (isAssignmentExpression(variable.initializer) - && isIdentifier(variable.initializer.left) - && isClassLike(variable.initializer.right))); - } - - function isTypeScriptClassWrapper(node: Node) { - const call = tryCast(node, isCallExpression); - if (!call || isParseTreeNode(call) || - some(call.typeArguments) || - some(call.arguments)) { - return false; - } - - const func = tryCast(skipOuterExpressions(call.expression), isFunctionExpression); - if (!func || isParseTreeNode(func) || - some(func.typeParameters) || - some(func.parameters) || - func.type || - !func.body) { - return false; - } - - const statements = func.body.statements; - if (statements.length < 2) { - return false; - } - - const firstStatement = statements[0]; - if (isParseTreeNode(firstStatement) || - !isClassLike(firstStatement) && - !isClassLikeVariableStatement(firstStatement)) { - return false; - } - - const lastStatement = elementAt(statements, -1); - const returnStatement = tryCast(isVariableStatement(lastStatement) ? elementAt(statements, -2) : lastStatement, isReturnStatement); - if (!returnStatement || - !returnStatement.expression || - !isIdentifier(skipOuterExpressions(returnStatement.expression))) { - return false; - } - - return true; - } - function shouldVisitNode(node: Node): boolean { return (node.transformFlags & TransformFlags.ContainsES2015) !== 0 || convertedLoopState !== undefined || (hierarchyFacts & HierarchyFacts.ConstructorWithCapturedSuper && (isStatement(node) || (node.kind === SyntaxKind.Block))) || (isIterationStatement(node, /*lookInLabeledStatements*/ false) && shouldConvertIterationStatementBody(node)) - || isTypeScriptClassWrapper(node); + || (getEmitFlags(node) & EmitFlags.TypeScriptClassWrapper) !== 0; } function visitor(node: Node): VisitResult { @@ -3308,13 +3256,14 @@ namespace ts { * @param node a CallExpression. */ function visitCallExpression(node: CallExpression) { - if (isTypeScriptClassWrapper(node)) { + if (getEmitFlags(node) & EmitFlags.TypeScriptClassWrapper) { return visitTypeScriptClassWrapper(node); } if (node.transformFlags & TransformFlags.ES2015) { return visitCallExpressionWithPotentialCapturedThisAssignment(node, /*assignToCapturedThis*/ true); } + return updateCall( node, visitNode(node.expression, callExpressionVisitor, isExpression), @@ -3357,7 +3306,7 @@ namespace ts { // We skip any outer expressions in a number of places to get to the innermost // expression, but we will restore them later to preserve comments and source maps. - const body = cast(skipOuterExpressions(node.expression), isFunctionExpression).body; + const body = cast(cast(skipOuterExpressions(node.expression), isArrowFunction).body, isBlock); // The class statements are the statements generated by visiting the first statement of the // body (1), while all other statements are added to remainingStatements (2) diff --git a/src/compiler/transformers/ts.ts b/src/compiler/transformers/ts.ts index 4c679ea1eee..71a3c7dcb7a 100644 --- a/src/compiler/transformers/ts.ts +++ b/src/compiler/transformers/ts.ts @@ -613,13 +613,16 @@ namespace ts { addRange(statements, context.endLexicalEnvironment()); + const iife = createImmediatelyInvokedArrowFunction(statements); + setEmitFlags(iife, EmitFlags.TypeScriptClassWrapper); + const varStatement = createVariableStatement( /*modifiers*/ undefined, createVariableDeclarationList([ createVariableDeclaration( getLocalName(node, /*allowComments*/ false, /*allowSourceMaps*/ false), /*type*/ undefined, - createImmediatelyInvokedFunctionExpression(statements) + iife ) ]) ); diff --git a/src/compiler/types.ts b/src/compiler/types.ts index fe18839b904..3ef38e0e4b7 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -4184,6 +4184,7 @@ namespace ts { HasEndOfDeclarationMarker = 1 << 22, // Declaration has an associated NotEmittedStatement to mark the end of the declaration Iterator = 1 << 23, // The expression to a `yield*` should be treated as an Iterator when down-leveling, not an Iterable. NoAsciiEscaping = 1 << 24, // When synthesizing nodes that lack an original node or textSourceNode, we want to write the text on the node with ASCII escaping substitutions. + /*@internal*/ TypeScriptClassWrapper = 1 << 25, // The node is an IIFE class wrapper created by the ts transform. } export interface EmitHelper { diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index d906ab4f6b1..de752faaf6a 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -2068,9 +2068,9 @@ namespace ts { || kind === SyntaxKind.SourceFile; } - export function nodeIsSynthesized(node: TextRange): boolean { - return positionIsSynthesized(node.pos) - || positionIsSynthesized(node.end); + export function nodeIsSynthesized(range: TextRange): boolean { + return positionIsSynthesized(range.pos) + || positionIsSynthesized(range.end); } export function getOriginalSourceFile(sourceFile: SourceFile) { diff --git a/tests/baselines/reference/decoratorOnClassMethod11.js b/tests/baselines/reference/decoratorOnClassMethod11.js index 2600681c647..e29c4076c91 100644 --- a/tests/baselines/reference/decoratorOnClassMethod11.js +++ b/tests/baselines/reference/decoratorOnClassMethod11.js @@ -17,6 +17,7 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key, }; var M; (function (M) { + var _this = this; var C = /** @class */ (function () { function C() { } diff --git a/tests/baselines/reference/decoratorOnClassMethod12.js b/tests/baselines/reference/decoratorOnClassMethod12.js index 494cb083a20..0063e0225bc 100644 --- a/tests/baselines/reference/decoratorOnClassMethod12.js +++ b/tests/baselines/reference/decoratorOnClassMethod12.js @@ -28,6 +28,7 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key, }; var M; (function (M) { + var _this = this; var S = /** @class */ (function () { function S() { } diff --git a/tests/baselines/reference/inferringClassMembersFromAssignments.js b/tests/baselines/reference/inferringClassMembersFromAssignments.js index 3fa72bce2cf..90f87734b2e 100644 --- a/tests/baselines/reference/inferringClassMembersFromAssignments.js +++ b/tests/baselines/reference/inferringClassMembersFromAssignments.js @@ -124,6 +124,7 @@ var stringOrNumberOrUndefined = C.inStaticNestedArrowFunction; //// [output.js] +var _this = this; var C = /** @class */ (function () { function C() { var _this = this; diff --git a/tests/baselines/reference/superAccess2.js b/tests/baselines/reference/superAccess2.js index de755361a16..d3aac217e22 100644 --- a/tests/baselines/reference/superAccess2.js +++ b/tests/baselines/reference/superAccess2.js @@ -35,6 +35,7 @@ var __extends = (this && this.__extends) || (function () { d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); }; })(); +var _this = this; var P = /** @class */ (function () { function P() { } diff --git a/tests/baselines/reference/thisInArrowFunctionInStaticInitializer1.js b/tests/baselines/reference/thisInArrowFunctionInStaticInitializer1.js index 97e958ace5e..2d58fd455ac 100644 --- a/tests/baselines/reference/thisInArrowFunctionInStaticInitializer1.js +++ b/tests/baselines/reference/thisInArrowFunctionInStaticInitializer1.js @@ -9,6 +9,7 @@ class Vector { } //// [thisInArrowFunctionInStaticInitializer1.js] +var _this = this; function log(a) { } var Vector = /** @class */ (function () { function Vector() { diff --git a/tests/baselines/reference/thisInConstructorParameter2.js b/tests/baselines/reference/thisInConstructorParameter2.js index 6a27183800c..a5e4f4d8b57 100644 --- a/tests/baselines/reference/thisInConstructorParameter2.js +++ b/tests/baselines/reference/thisInConstructorParameter2.js @@ -10,6 +10,7 @@ class P { } //// [thisInConstructorParameter2.js] +var _this = this; var P = /** @class */ (function () { function P(z, zz) { if (z === void 0) { z = this; } diff --git a/tests/baselines/reference/thisInInvalidContexts.js b/tests/baselines/reference/thisInInvalidContexts.js index 635eff25cf4..1e24cc4d9e2 100644 --- a/tests/baselines/reference/thisInInvalidContexts.js +++ b/tests/baselines/reference/thisInInvalidContexts.js @@ -59,6 +59,7 @@ var __extends = (this && this.__extends) || (function () { d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); }; })(); +var _this = this; //'this' in static member initializer var ErrClass1 = /** @class */ (function () { function ErrClass1() { diff --git a/tests/baselines/reference/thisInInvalidContextsExternalModule.js b/tests/baselines/reference/thisInInvalidContextsExternalModule.js index ea69a582730..daadace2cfa 100644 --- a/tests/baselines/reference/thisInInvalidContextsExternalModule.js +++ b/tests/baselines/reference/thisInInvalidContextsExternalModule.js @@ -60,6 +60,7 @@ var __extends = (this && this.__extends) || (function () { d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); }; })(); +var _this = this; //'this' in static member initializer var ErrClass1 = /** @class */ (function () { function ErrClass1() { diff --git a/tests/baselines/reference/thisInOuterClassBody.js b/tests/baselines/reference/thisInOuterClassBody.js index 2b4e3a1aabd..d5bf689b49a 100644 --- a/tests/baselines/reference/thisInOuterClassBody.js +++ b/tests/baselines/reference/thisInOuterClassBody.js @@ -21,6 +21,7 @@ class Foo { } //// [thisInOuterClassBody.js] +var _this = this; var Foo = /** @class */ (function () { function Foo() { this.x = this; diff --git a/tests/baselines/reference/typeOfThisInStaticMembers2.js b/tests/baselines/reference/typeOfThisInStaticMembers2.js index 72243cdd3e6..1cfec3a3fa1 100644 --- a/tests/baselines/reference/typeOfThisInStaticMembers2.js +++ b/tests/baselines/reference/typeOfThisInStaticMembers2.js @@ -8,6 +8,7 @@ class C2 { } //// [typeOfThisInStaticMembers2.js] +var _this = this; var C = /** @class */ (function () { function C() { } From ccd0158c406727d7a1ec81bdc579e8c6e6607f9d Mon Sep 17 00:00:00 2001 From: Ron Buckton Date: Thu, 24 Aug 2017 15:06:06 -0700 Subject: [PATCH 09/26] Added additional test --- .../reference/asyncArrowInClassES5.js | 21 +++++++++++++++++++ .../reference/asyncArrowInClassES5.symbols | 12 +++++++++++ .../reference/asyncArrowInClassES5.types | 13 ++++++++++++ tests/cases/compiler/asyncArrowInClassES5.ts | 9 ++++++++ 4 files changed, 55 insertions(+) create mode 100644 tests/baselines/reference/asyncArrowInClassES5.js create mode 100644 tests/baselines/reference/asyncArrowInClassES5.symbols create mode 100644 tests/baselines/reference/asyncArrowInClassES5.types create mode 100644 tests/cases/compiler/asyncArrowInClassES5.ts diff --git a/tests/baselines/reference/asyncArrowInClassES5.js b/tests/baselines/reference/asyncArrowInClassES5.js new file mode 100644 index 00000000000..f0204f7319b --- /dev/null +++ b/tests/baselines/reference/asyncArrowInClassES5.js @@ -0,0 +1,21 @@ +//// [asyncArrowInClassES5.ts] +// https://github.com/Microsoft/TypeScript/issues/16924 +// Should capture `this` + +class Test { + static member = async (x: string) => { }; +} + + +//// [asyncArrowInClassES5.js] +// https://github.com/Microsoft/TypeScript/issues/16924 +// Should capture `this` +var _this = this; +var Test = /** @class */ (function () { + function Test() { + } + Test.member = function (x) { return __awaiter(_this, void 0, void 0, function () { return __generator(this, function (_a) { + return [2 /*return*/]; + }); }); }; + return Test; +}()); diff --git a/tests/baselines/reference/asyncArrowInClassES5.symbols b/tests/baselines/reference/asyncArrowInClassES5.symbols new file mode 100644 index 00000000000..a6cc9f75d6e --- /dev/null +++ b/tests/baselines/reference/asyncArrowInClassES5.symbols @@ -0,0 +1,12 @@ +=== tests/cases/compiler/asyncArrowInClassES5.ts === +// https://github.com/Microsoft/TypeScript/issues/16924 +// Should capture `this` + +class Test { +>Test : Symbol(Test, Decl(asyncArrowInClassES5.ts, 0, 0)) + + static member = async (x: string) => { }; +>member : Symbol(Test.member, Decl(asyncArrowInClassES5.ts, 3, 12)) +>x : Symbol(x, Decl(asyncArrowInClassES5.ts, 4, 27)) +} + diff --git a/tests/baselines/reference/asyncArrowInClassES5.types b/tests/baselines/reference/asyncArrowInClassES5.types new file mode 100644 index 00000000000..8da6d5d2eaf --- /dev/null +++ b/tests/baselines/reference/asyncArrowInClassES5.types @@ -0,0 +1,13 @@ +=== tests/cases/compiler/asyncArrowInClassES5.ts === +// https://github.com/Microsoft/TypeScript/issues/16924 +// Should capture `this` + +class Test { +>Test : Test + + static member = async (x: string) => { }; +>member : (x: string) => Promise +>async (x: string) => { } : (x: string) => Promise +>x : string +} + diff --git a/tests/cases/compiler/asyncArrowInClassES5.ts b/tests/cases/compiler/asyncArrowInClassES5.ts new file mode 100644 index 00000000000..2712372d357 --- /dev/null +++ b/tests/cases/compiler/asyncArrowInClassES5.ts @@ -0,0 +1,9 @@ +// @noEmitHelpers: true +// @lib: es2015 +// @target: es5 +// https://github.com/Microsoft/TypeScript/issues/16924 +// Should capture `this` + +class Test { + static member = async (x: string) => { }; +} From 2f1bd8cff91210584f5952e5228050aebb83122b Mon Sep 17 00:00:00 2001 From: Wesley Wigham Date: Thu, 24 Aug 2017 15:52:04 -0700 Subject: [PATCH 10/26] Escape \0 followed by a number as a hex escape to avoid printing an octal literal (#18026) --- src/compiler/utilities.ts | 7 ++++++- ...uldNotPrintNullEscapesIntoOctalLiterals.js | 15 +++++++++++++++ ...tPrintNullEscapesIntoOctalLiterals.symbols | 8 ++++++++ ...NotPrintNullEscapesIntoOctalLiterals.types | 19 +++++++++++++++++++ ...uldNotPrintNullEscapesIntoOctalLiterals.ts | 6 ++++++ 5 files changed, 54 insertions(+), 1 deletion(-) create mode 100644 tests/baselines/reference/shouldNotPrintNullEscapesIntoOctalLiterals.js create mode 100644 tests/baselines/reference/shouldNotPrintNullEscapesIntoOctalLiterals.symbols create mode 100644 tests/baselines/reference/shouldNotPrintNullEscapesIntoOctalLiterals.types create mode 100644 tests/cases/compiler/shouldNotPrintNullEscapesIntoOctalLiterals.ts diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index d906ab4f6b1..6d53f9e8e99 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -2380,6 +2380,7 @@ namespace ts { "\u2029": "\\u2029", // paragraphSeparator "\u0085": "\\u0085" // nextLine }); + const escapedNullRegExp = /\\0[0-9]/g; /** * Based heavily on the abstract 'Quote'/'QuoteJSONString' operation from ECMA-262 (24.3.2.2), @@ -2391,7 +2392,11 @@ namespace ts { quoteChar === CharacterCodes.backtick ? backtickQuoteEscapedCharsRegExp : quoteChar === CharacterCodes.singleQuote ? singleQuoteEscapedCharsRegExp : doubleQuoteEscapedCharsRegExp; - return s.replace(escapedCharsRegExp, getReplacement); + return s.replace(escapedCharsRegExp, getReplacement).replace(escapedNullRegExp, nullReplacement); + } + + function nullReplacement(c: string) { + return "\\x00" + c.charAt(c.length - 1); } function getReplacement(c: string) { diff --git a/tests/baselines/reference/shouldNotPrintNullEscapesIntoOctalLiterals.js b/tests/baselines/reference/shouldNotPrintNullEscapesIntoOctalLiterals.js new file mode 100644 index 00000000000..4654558135c --- /dev/null +++ b/tests/baselines/reference/shouldNotPrintNullEscapesIntoOctalLiterals.js @@ -0,0 +1,15 @@ +//// [shouldNotPrintNullEscapesIntoOctalLiterals.ts] +"use strict"; +`\x001`; +`\u00001`; +`\u{00000000}1`; +`\u{000000}1`; +`\u{0}1`; + +//// [shouldNotPrintNullEscapesIntoOctalLiterals.js] +"use strict"; +"\x001"; +"\x001"; +"\x001"; +"\x001"; +"\x001"; diff --git a/tests/baselines/reference/shouldNotPrintNullEscapesIntoOctalLiterals.symbols b/tests/baselines/reference/shouldNotPrintNullEscapesIntoOctalLiterals.symbols new file mode 100644 index 00000000000..3ffb259aed0 --- /dev/null +++ b/tests/baselines/reference/shouldNotPrintNullEscapesIntoOctalLiterals.symbols @@ -0,0 +1,8 @@ +=== tests/cases/compiler/shouldNotPrintNullEscapesIntoOctalLiterals.ts === +"use strict"; +No type information for this code.`\x001`; +No type information for this code.`\u00001`; +No type information for this code.`\u{00000000}1`; +No type information for this code.`\u{000000}1`; +No type information for this code.`\u{0}1`; +No type information for this code. \ No newline at end of file diff --git a/tests/baselines/reference/shouldNotPrintNullEscapesIntoOctalLiterals.types b/tests/baselines/reference/shouldNotPrintNullEscapesIntoOctalLiterals.types new file mode 100644 index 00000000000..8f76e662558 --- /dev/null +++ b/tests/baselines/reference/shouldNotPrintNullEscapesIntoOctalLiterals.types @@ -0,0 +1,19 @@ +=== tests/cases/compiler/shouldNotPrintNullEscapesIntoOctalLiterals.ts === +"use strict"; +>"use strict" : "use strict" + +`\x001`; +>`\x001` : "\x001" + +`\u00001`; +>`\u00001` : "\x001" + +`\u{00000000}1`; +>`\u{00000000}1` : "\x001" + +`\u{000000}1`; +>`\u{000000}1` : "\x001" + +`\u{0}1`; +>`\u{0}1` : "\x001" + diff --git a/tests/cases/compiler/shouldNotPrintNullEscapesIntoOctalLiterals.ts b/tests/cases/compiler/shouldNotPrintNullEscapesIntoOctalLiterals.ts new file mode 100644 index 00000000000..fbbab43319a --- /dev/null +++ b/tests/cases/compiler/shouldNotPrintNullEscapesIntoOctalLiterals.ts @@ -0,0 +1,6 @@ +"use strict"; +`\x001`; +`\u00001`; +`\u{00000000}1`; +`\u{000000}1`; +`\u{0}1`; \ No newline at end of file From 336df751eab01595e451b0de6e5c9ecf551083b0 Mon Sep 17 00:00:00 2001 From: Wesley Wigham Date: Thu, 24 Aug 2017 15:53:09 -0700 Subject: [PATCH 11/26] Fix issue #16803 do not error on getters/setters (#18031) --- src/compiler/checker.ts | 17 ++++++++++----- src/compiler/core.ts | 4 ++++ .../exportEqualsClassNoRedeclarationError.js | 19 +++++++++++++++++ ...ortEqualsClassNoRedeclarationError.symbols | 17 +++++++++++++++ ...xportEqualsClassNoRedeclarationError.types | 18 ++++++++++++++++ ...rtEqualsClassRedeclarationError.errors.txt | 21 +++++++++++++++++++ .../exportEqualsClassRedeclarationError.js | 21 +++++++++++++++++++ .../exportEqualsClassNoRedeclarationError.ts | 10 +++++++++ .../exportEqualsClassRedeclarationError.ts | 11 ++++++++++ 9 files changed, 133 insertions(+), 5 deletions(-) create mode 100644 tests/baselines/reference/exportEqualsClassNoRedeclarationError.js create mode 100644 tests/baselines/reference/exportEqualsClassNoRedeclarationError.symbols create mode 100644 tests/baselines/reference/exportEqualsClassNoRedeclarationError.types create mode 100644 tests/baselines/reference/exportEqualsClassRedeclarationError.errors.txt create mode 100644 tests/baselines/reference/exportEqualsClassRedeclarationError.js create mode 100644 tests/cases/compiler/exportEqualsClassNoRedeclarationError.ts create mode 100644 tests/cases/compiler/exportEqualsClassRedeclarationError.ts diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 24a9a868c4a..21db77b5e39 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -497,6 +497,8 @@ namespace ts { const builtinGlobals = createSymbolTable(); builtinGlobals.set(undefinedSymbol.escapedName, undefinedSymbol); + const isNotOverloadAndNotAccessor = and(isNotOverload, isNotAccessor); + initializeTypeChecker(); return checker; @@ -22257,7 +22259,7 @@ namespace ts { if (flags & (SymbolFlags.Namespace | SymbolFlags.Interface | SymbolFlags.Enum)) { return; } - const exportedDeclarationsCount = countWhere(declarations, isNotOverload); + const exportedDeclarationsCount = countWhere(declarations, isNotOverloadAndNotAccessor); if (flags & SymbolFlags.TypeAlias && exportedDeclarationsCount <= 2) { // it is legal to merge type alias with other values // so count should be either 1 (just type alias) or 2 (type alias + merged value) @@ -22273,11 +22275,16 @@ namespace ts { }); links.exportsChecked = true; } + } - function isNotOverload(declaration: Declaration): boolean { - return (declaration.kind !== SyntaxKind.FunctionDeclaration && declaration.kind !== SyntaxKind.MethodDeclaration) || - !!(declaration as FunctionDeclaration).body; - } + function isNotAccessor(declaration: Declaration): boolean { + // Accessors check for their own matching duplicates, and in contexts where they are valid, there are already duplicate identifier checks + return !isAccessor(declaration); + } + + function isNotOverload(declaration: Declaration): boolean { + return (declaration.kind !== SyntaxKind.FunctionDeclaration && declaration.kind !== SyntaxKind.MethodDeclaration) || + !!(declaration as FunctionDeclaration).body; } function checkSourceElement(node: Node): void { diff --git a/src/compiler/core.ts b/src/compiler/core.ts index 6eb17f1fb78..f94ac9a75c5 100644 --- a/src/compiler/core.ts +++ b/src/compiler/core.ts @@ -2626,4 +2626,8 @@ namespace ts { export function isCheckJsEnabledForFile(sourceFile: SourceFile, compilerOptions: CompilerOptions) { return sourceFile.checkJsDirective ? sourceFile.checkJsDirective.enabled : compilerOptions.checkJs; } + + export function and(f: (arg: T) => boolean, g: (arg: T) => boolean) { + return (arg: T) => f(arg) && g(arg); + } } diff --git a/tests/baselines/reference/exportEqualsClassNoRedeclarationError.js b/tests/baselines/reference/exportEqualsClassNoRedeclarationError.js new file mode 100644 index 00000000000..834d10a48d6 --- /dev/null +++ b/tests/baselines/reference/exportEqualsClassNoRedeclarationError.js @@ -0,0 +1,19 @@ +//// [exportEqualsClassNoRedeclarationError.ts] +class SomeClass { + static get someProp(): number { + return 0; + } + + static set someProp(value: number) {} +} +export = SomeClass; + +//// [exportEqualsClassNoRedeclarationError.js] +"use strict"; +class SomeClass { + static get someProp() { + return 0; + } + static set someProp(value) { } +} +module.exports = SomeClass; diff --git a/tests/baselines/reference/exportEqualsClassNoRedeclarationError.symbols b/tests/baselines/reference/exportEqualsClassNoRedeclarationError.symbols new file mode 100644 index 00000000000..386efa1bdaa --- /dev/null +++ b/tests/baselines/reference/exportEqualsClassNoRedeclarationError.symbols @@ -0,0 +1,17 @@ +=== tests/cases/compiler/exportEqualsClassNoRedeclarationError.ts === +class SomeClass { +>SomeClass : Symbol(SomeClass, Decl(exportEqualsClassNoRedeclarationError.ts, 0, 0)) + + static get someProp(): number { +>someProp : Symbol(SomeClass.someProp, Decl(exportEqualsClassNoRedeclarationError.ts, 0, 17), Decl(exportEqualsClassNoRedeclarationError.ts, 3, 5)) + + return 0; + } + + static set someProp(value: number) {} +>someProp : Symbol(SomeClass.someProp, Decl(exportEqualsClassNoRedeclarationError.ts, 0, 17), Decl(exportEqualsClassNoRedeclarationError.ts, 3, 5)) +>value : Symbol(value, Decl(exportEqualsClassNoRedeclarationError.ts, 5, 24)) +} +export = SomeClass; +>SomeClass : Symbol(SomeClass, Decl(exportEqualsClassNoRedeclarationError.ts, 0, 0)) + diff --git a/tests/baselines/reference/exportEqualsClassNoRedeclarationError.types b/tests/baselines/reference/exportEqualsClassNoRedeclarationError.types new file mode 100644 index 00000000000..cba7b57a4e8 --- /dev/null +++ b/tests/baselines/reference/exportEqualsClassNoRedeclarationError.types @@ -0,0 +1,18 @@ +=== tests/cases/compiler/exportEqualsClassNoRedeclarationError.ts === +class SomeClass { +>SomeClass : SomeClass + + static get someProp(): number { +>someProp : number + + return 0; +>0 : 0 + } + + static set someProp(value: number) {} +>someProp : number +>value : number +} +export = SomeClass; +>SomeClass : SomeClass + diff --git a/tests/baselines/reference/exportEqualsClassRedeclarationError.errors.txt b/tests/baselines/reference/exportEqualsClassRedeclarationError.errors.txt new file mode 100644 index 00000000000..4eccf0cc1ca --- /dev/null +++ b/tests/baselines/reference/exportEqualsClassRedeclarationError.errors.txt @@ -0,0 +1,21 @@ +tests/cases/compiler/exportEqualsClassRedeclarationError.ts(2,16): error TS2300: Duplicate identifier 'someProp'. +tests/cases/compiler/exportEqualsClassRedeclarationError.ts(6,16): error TS2300: Duplicate identifier 'someProp'. +tests/cases/compiler/exportEqualsClassRedeclarationError.ts(7,16): error TS2300: Duplicate identifier 'someProp'. + + +==== tests/cases/compiler/exportEqualsClassRedeclarationError.ts (3 errors) ==== + class SomeClass { + static get someProp(): number { + ~~~~~~~~ +!!! error TS2300: Duplicate identifier 'someProp'. + return 0; + } + + static set someProp(value: number) {} + ~~~~~~~~ +!!! error TS2300: Duplicate identifier 'someProp'. + static set someProp(value: number) {} + ~~~~~~~~ +!!! error TS2300: Duplicate identifier 'someProp'. + } + export = SomeClass; \ No newline at end of file diff --git a/tests/baselines/reference/exportEqualsClassRedeclarationError.js b/tests/baselines/reference/exportEqualsClassRedeclarationError.js new file mode 100644 index 00000000000..f0134c46626 --- /dev/null +++ b/tests/baselines/reference/exportEqualsClassRedeclarationError.js @@ -0,0 +1,21 @@ +//// [exportEqualsClassRedeclarationError.ts] +class SomeClass { + static get someProp(): number { + return 0; + } + + static set someProp(value: number) {} + static set someProp(value: number) {} +} +export = SomeClass; + +//// [exportEqualsClassRedeclarationError.js] +"use strict"; +class SomeClass { + static get someProp() { + return 0; + } + static set someProp(value) { } + static set someProp(value) { } +} +module.exports = SomeClass; diff --git a/tests/cases/compiler/exportEqualsClassNoRedeclarationError.ts b/tests/cases/compiler/exportEqualsClassNoRedeclarationError.ts new file mode 100644 index 00000000000..0e5c5824111 --- /dev/null +++ b/tests/cases/compiler/exportEqualsClassNoRedeclarationError.ts @@ -0,0 +1,10 @@ +// @target: es6 +// @module: commonjs +class SomeClass { + static get someProp(): number { + return 0; + } + + static set someProp(value: number) {} +} +export = SomeClass; \ No newline at end of file diff --git a/tests/cases/compiler/exportEqualsClassRedeclarationError.ts b/tests/cases/compiler/exportEqualsClassRedeclarationError.ts new file mode 100644 index 00000000000..237aca399f0 --- /dev/null +++ b/tests/cases/compiler/exportEqualsClassRedeclarationError.ts @@ -0,0 +1,11 @@ +// @target: es6 +// @module: commonjs +class SomeClass { + static get someProp(): number { + return 0; + } + + static set someProp(value: number) {} + static set someProp(value: number) {} +} +export = SomeClass; \ No newline at end of file From f824e7214ddda789baa6379ff599c5f5012a42b6 Mon Sep 17 00:00:00 2001 From: Wesley Wigham Date: Thu, 24 Aug 2017 16:48:11 -0700 Subject: [PATCH 12/26] Give mapped type properties a synthetic declaration name (#18023) * Escape symbol names which are not valid identifiers and wrap them in quotes * Pass forward type, do work in getNameOfSymbol * Minimal test * Fix nit --- src/compiler/checker.ts | 16 +++++++++++++++- src/compiler/types.ts | 1 + .../reference/declarationQuotedMembers.js | 17 +++++++++++++++++ .../reference/declarationQuotedMembers.symbols | 9 +++++++++ .../reference/declarationQuotedMembers.types | 9 +++++++++ .../cases/compiler/declarationQuotedMembers.ts | 3 +++ 6 files changed, 54 insertions(+), 1 deletion(-) create mode 100644 tests/baselines/reference/declarationQuotedMembers.js create mode 100644 tests/baselines/reference/declarationQuotedMembers.symbols create mode 100644 tests/baselines/reference/declarationQuotedMembers.types create mode 100644 tests/cases/compiler/declarationQuotedMembers.ts diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 21db77b5e39..55d26bb48ea 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -3114,6 +3114,12 @@ namespace ts { return "(Anonymous function)"; } } + if ((symbol as TransientSymbol).syntheticLiteralTypeOrigin) { + const stringValue = (symbol as TransientSymbol).syntheticLiteralTypeOrigin.value; + if (!isIdentifierText(stringValue, compilerOptions.target)) { + return `"${escapeString(stringValue, CharacterCodes.doubleQuote)}"`; + } + } return unescapeLeadingUnderscores(symbol.escapedName); } @@ -5724,7 +5730,14 @@ namespace ts { } setStructuredTypeMembers(type, members, emptyArray, emptyArray, stringIndexInfo, undefined); - function addMemberForKeyType(t: Type, propertySymbol?: Symbol) { + function addMemberForKeyType(t: Type, propertySymbolOrIndex?: Symbol | number) { + let propertySymbol: Symbol; + // forEachType delegates to forEach, which calls with a numeric second argument + // the type system currently doesn't catch this incompatibility, so we annotate + // the function ourselves to indicate the runtime behavior and deal with it here + if (typeof propertySymbolOrIndex === "object") { + propertySymbol = propertySymbolOrIndex; + } // Create a mapper from T to the current iteration type constituent. Then, if the // mapped type is itself an instantiated type, combine the iteration mapper with the // instantiation mapper. @@ -5744,6 +5757,7 @@ namespace ts { prop.syntheticOrigin = propertySymbol; prop.declarations = propertySymbol.declarations; } + prop.syntheticLiteralTypeOrigin = t as StringLiteralType; members.set(propName, prop); } else if (t.flags & TypeFlags.String) { diff --git a/src/compiler/types.ts b/src/compiler/types.ts index fe18839b904..e3c8661deba 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -2972,6 +2972,7 @@ namespace ts { leftSpread?: Symbol; // Left source for synthetic spread property rightSpread?: Symbol; // Right source for synthetic spread property syntheticOrigin?: Symbol; // For a property on a mapped or spread type, points back to the original property + syntheticLiteralTypeOrigin?: StringLiteralType; // For a property on a mapped type, indicates the type whose text to use as the declaration name, instead of the symbol name isDiscriminantProperty?: boolean; // True if discriminant synthetic property resolvedExports?: SymbolTable; // Resolved exports of module exportsChecked?: boolean; // True if exports of external module have been checked diff --git a/tests/baselines/reference/declarationQuotedMembers.js b/tests/baselines/reference/declarationQuotedMembers.js new file mode 100644 index 00000000000..ab40231eb3e --- /dev/null +++ b/tests/baselines/reference/declarationQuotedMembers.js @@ -0,0 +1,17 @@ +//// [declarationQuotedMembers.ts] +export declare const mapped: { [K in 'a-b-c']: number } +export const example = mapped; + +//// [declarationQuotedMembers.js] +"use strict"; +exports.__esModule = true; +exports.example = exports.mapped; + + +//// [declarationQuotedMembers.d.ts] +export declare const mapped: { + [K in 'a-b-c']: number; +}; +export declare const example: { + "a-b-c": number; +}; diff --git a/tests/baselines/reference/declarationQuotedMembers.symbols b/tests/baselines/reference/declarationQuotedMembers.symbols new file mode 100644 index 00000000000..3bf1a2829b0 --- /dev/null +++ b/tests/baselines/reference/declarationQuotedMembers.symbols @@ -0,0 +1,9 @@ +=== tests/cases/compiler/declarationQuotedMembers.ts === +export declare const mapped: { [K in 'a-b-c']: number } +>mapped : Symbol(mapped, Decl(declarationQuotedMembers.ts, 0, 20)) +>K : Symbol(K, Decl(declarationQuotedMembers.ts, 0, 32)) + +export const example = mapped; +>example : Symbol(example, Decl(declarationQuotedMembers.ts, 1, 12)) +>mapped : Symbol(mapped, Decl(declarationQuotedMembers.ts, 0, 20)) + diff --git a/tests/baselines/reference/declarationQuotedMembers.types b/tests/baselines/reference/declarationQuotedMembers.types new file mode 100644 index 00000000000..c9f6c047b7e --- /dev/null +++ b/tests/baselines/reference/declarationQuotedMembers.types @@ -0,0 +1,9 @@ +=== tests/cases/compiler/declarationQuotedMembers.ts === +export declare const mapped: { [K in 'a-b-c']: number } +>mapped : { a-b-c: number; } +>K : K + +export const example = mapped; +>example : { a-b-c: number; } +>mapped : { a-b-c: number; } + diff --git a/tests/cases/compiler/declarationQuotedMembers.ts b/tests/cases/compiler/declarationQuotedMembers.ts new file mode 100644 index 00000000000..08bae2c3754 --- /dev/null +++ b/tests/cases/compiler/declarationQuotedMembers.ts @@ -0,0 +1,3 @@ +// @declaration: true +export declare const mapped: { [K in 'a-b-c']: number } +export const example = mapped; \ No newline at end of file From 643a7e7e3326a54ddf6fa590146ac8cfde4c6e98 Mon Sep 17 00:00:00 2001 From: Wesley Wigham Date: Thu, 24 Aug 2017 17:08:57 -0700 Subject: [PATCH 13/26] Call dynamic import transform on expression used by export equal statement (#18028) * Call dynamic import transform on expression used by export equal statement * Use Debug.fail --- src/compiler/transformers/module/module.ts | 46 +++++++++++-------- .../importCallExpressionInExportEqualsAMD.js | 22 +++++++++ ...ortCallExpressionInExportEqualsAMD.symbols | 10 ++++ ...mportCallExpressionInExportEqualsAMD.types | 14 ++++++ .../importCallExpressionInExportEqualsCJS.js | 18 ++++++++ ...ortCallExpressionInExportEqualsCJS.symbols | 10 ++++ ...mportCallExpressionInExportEqualsCJS.types | 14 ++++++ .../importCallExpressionInExportEqualsUMD.js | 39 ++++++++++++++++ ...ortCallExpressionInExportEqualsUMD.symbols | 10 ++++ ...mportCallExpressionInExportEqualsUMD.types | 14 ++++++ .../importCallExpressionInExportEqualsAMD.ts | 9 ++++ .../importCallExpressionInExportEqualsCJS.ts | 9 ++++ .../importCallExpressionInExportEqualsUMD.ts | 9 ++++ 13 files changed, 204 insertions(+), 20 deletions(-) create mode 100644 tests/baselines/reference/importCallExpressionInExportEqualsAMD.js create mode 100644 tests/baselines/reference/importCallExpressionInExportEqualsAMD.symbols create mode 100644 tests/baselines/reference/importCallExpressionInExportEqualsAMD.types create mode 100644 tests/baselines/reference/importCallExpressionInExportEqualsCJS.js create mode 100644 tests/baselines/reference/importCallExpressionInExportEqualsCJS.symbols create mode 100644 tests/baselines/reference/importCallExpressionInExportEqualsCJS.types create mode 100644 tests/baselines/reference/importCallExpressionInExportEqualsUMD.js create mode 100644 tests/baselines/reference/importCallExpressionInExportEqualsUMD.symbols create mode 100644 tests/baselines/reference/importCallExpressionInExportEqualsUMD.types create mode 100644 tests/cases/conformance/dynamicImport/importCallExpressionInExportEqualsAMD.ts create mode 100644 tests/cases/conformance/dynamicImport/importCallExpressionInExportEqualsCJS.ts create mode 100644 tests/cases/conformance/dynamicImport/importCallExpressionInExportEqualsUMD.ts diff --git a/src/compiler/transformers/module/module.ts b/src/compiler/transformers/module/module.ts index a0f593e5111..23bae8714c8 100644 --- a/src/compiler/transformers/module/module.ts +++ b/src/compiler/transformers/module/module.ts @@ -430,26 +430,32 @@ namespace ts { */ function addExportEqualsIfNeeded(statements: Statement[], emitAsReturn: boolean) { if (currentModuleInfo.exportEquals) { - if (emitAsReturn) { - const statement = createReturn(currentModuleInfo.exportEquals.expression); - setTextRange(statement, currentModuleInfo.exportEquals); - setEmitFlags(statement, EmitFlags.NoTokenSourceMaps | EmitFlags.NoComments); - statements.push(statement); - } - else { - const statement = createStatement( - createAssignment( - createPropertyAccess( - createIdentifier("module"), - "exports" - ), - currentModuleInfo.exportEquals.expression - ) - ); + const expressionResult = importCallExpressionVisitor(currentModuleInfo.exportEquals.expression); + if (expressionResult) { + if (expressionResult instanceof Array) { + return Debug.fail("export= expression should never be replaced with multiple expressions!"); + } + if (emitAsReturn) { + const statement = createReturn(expressionResult); + setTextRange(statement, currentModuleInfo.exportEquals); + setEmitFlags(statement, EmitFlags.NoTokenSourceMaps | EmitFlags.NoComments); + statements.push(statement); + } + else { + const statement = createStatement( + createAssignment( + createPropertyAccess( + createIdentifier("module"), + "exports" + ), + expressionResult + ) + ); - setTextRange(statement, currentModuleInfo.exportEquals); - setEmitFlags(statement, EmitFlags.NoComments); - statements.push(statement); + setTextRange(statement, currentModuleInfo.exportEquals); + setEmitFlags(statement, EmitFlags.NoComments); + statements.push(statement); + } } } } @@ -497,7 +503,7 @@ namespace ts { } } - function importCallExpressionVisitor(node: Node): VisitResult { + function importCallExpressionVisitor(node: Expression): VisitResult { // This visitor does not need to descend into the tree if there is no dynamic import, // as export/import statements are only transformed at the top level of a file. if (!(node.transformFlags & TransformFlags.ContainsDynamicImport)) { diff --git a/tests/baselines/reference/importCallExpressionInExportEqualsAMD.js b/tests/baselines/reference/importCallExpressionInExportEqualsAMD.js new file mode 100644 index 00000000000..f2fda1fadd7 --- /dev/null +++ b/tests/baselines/reference/importCallExpressionInExportEqualsAMD.js @@ -0,0 +1,22 @@ +//// [tests/cases/conformance/dynamicImport/importCallExpressionInExportEqualsAMD.ts] //// + +//// [something.ts] +export = 42; + +//// [index.ts] +export = async function() { + const something = await import("./something"); +}; + +//// [something.js] +define(["require", "exports"], function (require, exports) { + "use strict"; + return 42; +}); +//// [index.js] +define(["require", "exports"], function (require, exports) { + "use strict"; + return async function () { + const something = await new Promise(function (resolve_1, reject_1) { require(["./something"], resolve_1, reject_1); }); + }; +}); diff --git a/tests/baselines/reference/importCallExpressionInExportEqualsAMD.symbols b/tests/baselines/reference/importCallExpressionInExportEqualsAMD.symbols new file mode 100644 index 00000000000..cd8d7d38041 --- /dev/null +++ b/tests/baselines/reference/importCallExpressionInExportEqualsAMD.symbols @@ -0,0 +1,10 @@ +=== tests/cases/conformance/dynamicImport/something.ts === +export = 42; +No type information for this code. +No type information for this code.=== tests/cases/conformance/dynamicImport/index.ts === +export = async function() { + const something = await import("./something"); +>something : Symbol(something, Decl(index.ts, 1, 9)) +>"./something" : Symbol("tests/cases/conformance/dynamicImport/something", Decl(something.ts, 0, 0)) + +}; diff --git a/tests/baselines/reference/importCallExpressionInExportEqualsAMD.types b/tests/baselines/reference/importCallExpressionInExportEqualsAMD.types new file mode 100644 index 00000000000..b590130d1cf --- /dev/null +++ b/tests/baselines/reference/importCallExpressionInExportEqualsAMD.types @@ -0,0 +1,14 @@ +=== tests/cases/conformance/dynamicImport/something.ts === +export = 42; +No type information for this code. +No type information for this code.=== tests/cases/conformance/dynamicImport/index.ts === +export = async function() { +>async function() { const something = await import("./something");} : () => Promise + + const something = await import("./something"); +>something : 42 +>await import("./something") : 42 +>import("./something") : Promise<42> +>"./something" : "./something" + +}; diff --git a/tests/baselines/reference/importCallExpressionInExportEqualsCJS.js b/tests/baselines/reference/importCallExpressionInExportEqualsCJS.js new file mode 100644 index 00000000000..5d7e2816116 --- /dev/null +++ b/tests/baselines/reference/importCallExpressionInExportEqualsCJS.js @@ -0,0 +1,18 @@ +//// [tests/cases/conformance/dynamicImport/importCallExpressionInExportEqualsCJS.ts] //// + +//// [something.ts] +export = 42; + +//// [index.ts] +export = async function() { + const something = await import("./something"); +}; + +//// [something.js] +"use strict"; +module.exports = 42; +//// [index.js] +"use strict"; +module.exports = async function () { + const something = await Promise.resolve().then(function () { return require("./something"); }); +}; diff --git a/tests/baselines/reference/importCallExpressionInExportEqualsCJS.symbols b/tests/baselines/reference/importCallExpressionInExportEqualsCJS.symbols new file mode 100644 index 00000000000..cd8d7d38041 --- /dev/null +++ b/tests/baselines/reference/importCallExpressionInExportEqualsCJS.symbols @@ -0,0 +1,10 @@ +=== tests/cases/conformance/dynamicImport/something.ts === +export = 42; +No type information for this code. +No type information for this code.=== tests/cases/conformance/dynamicImport/index.ts === +export = async function() { + const something = await import("./something"); +>something : Symbol(something, Decl(index.ts, 1, 9)) +>"./something" : Symbol("tests/cases/conformance/dynamicImport/something", Decl(something.ts, 0, 0)) + +}; diff --git a/tests/baselines/reference/importCallExpressionInExportEqualsCJS.types b/tests/baselines/reference/importCallExpressionInExportEqualsCJS.types new file mode 100644 index 00000000000..b590130d1cf --- /dev/null +++ b/tests/baselines/reference/importCallExpressionInExportEqualsCJS.types @@ -0,0 +1,14 @@ +=== tests/cases/conformance/dynamicImport/something.ts === +export = 42; +No type information for this code. +No type information for this code.=== tests/cases/conformance/dynamicImport/index.ts === +export = async function() { +>async function() { const something = await import("./something");} : () => Promise + + const something = await import("./something"); +>something : 42 +>await import("./something") : 42 +>import("./something") : Promise<42> +>"./something" : "./something" + +}; diff --git a/tests/baselines/reference/importCallExpressionInExportEqualsUMD.js b/tests/baselines/reference/importCallExpressionInExportEqualsUMD.js new file mode 100644 index 00000000000..e0c6e2a925f --- /dev/null +++ b/tests/baselines/reference/importCallExpressionInExportEqualsUMD.js @@ -0,0 +1,39 @@ +//// [tests/cases/conformance/dynamicImport/importCallExpressionInExportEqualsUMD.ts] //// + +//// [something.ts] +export = 42; + +//// [index.ts] +export = async function() { + const something = await import("./something"); +}; + +//// [something.js] +(function (factory) { + if (typeof module === "object" && typeof module.exports === "object") { + var v = factory(require, exports); + if (v !== undefined) module.exports = v; + } + else if (typeof define === "function" && define.amd) { + define(["require", "exports"], factory); + } +})(function (require, exports) { + "use strict"; + return 42; +}); +//// [index.js] +(function (factory) { + if (typeof module === "object" && typeof module.exports === "object") { + var v = factory(require, exports); + if (v !== undefined) module.exports = v; + } + else if (typeof define === "function" && define.amd) { + define(["require", "exports"], factory); + } +})(function (require, exports) { + "use strict"; + var __syncRequire = typeof module === "object" && typeof module.exports === "object"; + return async function () { + const something = await (__syncRequire ? Promise.resolve().then(function () { return require("./something"); }) : new Promise(function (resolve_1, reject_1) { require(["./something"], resolve_1, reject_1); })); + }; +}); diff --git a/tests/baselines/reference/importCallExpressionInExportEqualsUMD.symbols b/tests/baselines/reference/importCallExpressionInExportEqualsUMD.symbols new file mode 100644 index 00000000000..cd8d7d38041 --- /dev/null +++ b/tests/baselines/reference/importCallExpressionInExportEqualsUMD.symbols @@ -0,0 +1,10 @@ +=== tests/cases/conformance/dynamicImport/something.ts === +export = 42; +No type information for this code. +No type information for this code.=== tests/cases/conformance/dynamicImport/index.ts === +export = async function() { + const something = await import("./something"); +>something : Symbol(something, Decl(index.ts, 1, 9)) +>"./something" : Symbol("tests/cases/conformance/dynamicImport/something", Decl(something.ts, 0, 0)) + +}; diff --git a/tests/baselines/reference/importCallExpressionInExportEqualsUMD.types b/tests/baselines/reference/importCallExpressionInExportEqualsUMD.types new file mode 100644 index 00000000000..b590130d1cf --- /dev/null +++ b/tests/baselines/reference/importCallExpressionInExportEqualsUMD.types @@ -0,0 +1,14 @@ +=== tests/cases/conformance/dynamicImport/something.ts === +export = 42; +No type information for this code. +No type information for this code.=== tests/cases/conformance/dynamicImport/index.ts === +export = async function() { +>async function() { const something = await import("./something");} : () => Promise + + const something = await import("./something"); +>something : 42 +>await import("./something") : 42 +>import("./something") : Promise<42> +>"./something" : "./something" + +}; diff --git a/tests/cases/conformance/dynamicImport/importCallExpressionInExportEqualsAMD.ts b/tests/cases/conformance/dynamicImport/importCallExpressionInExportEqualsAMD.ts new file mode 100644 index 00000000000..77f348bd3fa --- /dev/null +++ b/tests/cases/conformance/dynamicImport/importCallExpressionInExportEqualsAMD.ts @@ -0,0 +1,9 @@ +// @module: amd +// @target: esnext +// @filename: something.ts +export = 42; + +// @filename: index.ts +export = async function() { + const something = await import("./something"); +}; \ No newline at end of file diff --git a/tests/cases/conformance/dynamicImport/importCallExpressionInExportEqualsCJS.ts b/tests/cases/conformance/dynamicImport/importCallExpressionInExportEqualsCJS.ts new file mode 100644 index 00000000000..efda80d4995 --- /dev/null +++ b/tests/cases/conformance/dynamicImport/importCallExpressionInExportEqualsCJS.ts @@ -0,0 +1,9 @@ +// @module: commonjs +// @target: esnext +// @filename: something.ts +export = 42; + +// @filename: index.ts +export = async function() { + const something = await import("./something"); +}; \ No newline at end of file diff --git a/tests/cases/conformance/dynamicImport/importCallExpressionInExportEqualsUMD.ts b/tests/cases/conformance/dynamicImport/importCallExpressionInExportEqualsUMD.ts new file mode 100644 index 00000000000..fc0865914fb --- /dev/null +++ b/tests/cases/conformance/dynamicImport/importCallExpressionInExportEqualsUMD.ts @@ -0,0 +1,9 @@ +// @module: umd +// @target: esnext +// @filename: something.ts +export = 42; + +// @filename: index.ts +export = async function() { + const something = await import("./something"); +}; \ No newline at end of file From 62eaaf92069dc450128cd93320db877460722fde Mon Sep 17 00:00:00 2001 From: Wesley Wigham Date: Thu, 24 Aug 2017 17:12:42 -0700 Subject: [PATCH 14/26] Fix crash when attempting to merge an import with a local declaration (#18032) * There should be no crash when attempting to merge an import with a local declaration * Show symbol has actually merged within the module --- src/compiler/checker.ts | 2 + .../noCrashOnImportShadowing.errors.txt | 33 +++++++++++++ .../reference/noCrashOnImportShadowing.js | 46 +++++++++++++++++++ .../compiler/noCrashOnImportShadowing.ts | 25 ++++++++++ 4 files changed, 106 insertions(+) create mode 100644 tests/baselines/reference/noCrashOnImportShadowing.errors.txt create mode 100644 tests/baselines/reference/noCrashOnImportShadowing.js create mode 100644 tests/cases/compiler/noCrashOnImportShadowing.ts diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 4bdfa4b7250..4405a31db28 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -19180,6 +19180,8 @@ namespace ts { : DeclarationSpaces.ExportNamespace; case SyntaxKind.ClassDeclaration: case SyntaxKind.EnumDeclaration: + // A NamespaceImport declares an Alias, which is allowed to merge with other values within the module + case SyntaxKind.NamespaceImport: return DeclarationSpaces.ExportType | DeclarationSpaces.ExportValue; case SyntaxKind.ImportEqualsDeclaration: let result = DeclarationSpaces.None; diff --git a/tests/baselines/reference/noCrashOnImportShadowing.errors.txt b/tests/baselines/reference/noCrashOnImportShadowing.errors.txt new file mode 100644 index 00000000000..c1d1dbcdef1 --- /dev/null +++ b/tests/baselines/reference/noCrashOnImportShadowing.errors.txt @@ -0,0 +1,33 @@ +tests/cases/compiler/index.ts(4,1): error TS2693: 'B' only refers to a type, but is being used as a value here. +tests/cases/compiler/index.ts(9,10): error TS2304: Cannot find name 'OriginalB'. + + +==== tests/cases/compiler/b.ts (0 errors) ==== + export const zzz = 123; + +==== tests/cases/compiler/a.ts (0 errors) ==== + import * as B from "./b"; + + interface B { + x: string; + } + + const x: B = { x: "" }; + B.zzz; + + export { B }; + +==== tests/cases/compiler/index.ts (2 errors) ==== + import { B } from "./a"; + + const x: B = { x: "" }; + B.zzz; + ~ +!!! error TS2693: 'B' only refers to a type, but is being used as a value here. + + import * as OriginalB from "./b"; + OriginalB.zzz; + + const y: OriginalB = x; + ~~~~~~~~~ +!!! error TS2304: Cannot find name 'OriginalB'. \ No newline at end of file diff --git a/tests/baselines/reference/noCrashOnImportShadowing.js b/tests/baselines/reference/noCrashOnImportShadowing.js new file mode 100644 index 00000000000..7ca2116954d --- /dev/null +++ b/tests/baselines/reference/noCrashOnImportShadowing.js @@ -0,0 +1,46 @@ +//// [tests/cases/compiler/noCrashOnImportShadowing.ts] //// + +//// [b.ts] +export const zzz = 123; + +//// [a.ts] +import * as B from "./b"; + +interface B { + x: string; +} + +const x: B = { x: "" }; +B.zzz; + +export { B }; + +//// [index.ts] +import { B } from "./a"; + +const x: B = { x: "" }; +B.zzz; + +import * as OriginalB from "./b"; +OriginalB.zzz; + +const y: OriginalB = x; + +//// [b.js] +"use strict"; +exports.__esModule = true; +exports.zzz = 123; +//// [a.js] +"use strict"; +exports.__esModule = true; +var B = require("./b"); +var x = { x: "" }; +B.zzz; +//// [index.js] +"use strict"; +exports.__esModule = true; +var x = { x: "" }; +B.zzz; +var OriginalB = require("./b"); +OriginalB.zzz; +var y = x; diff --git a/tests/cases/compiler/noCrashOnImportShadowing.ts b/tests/cases/compiler/noCrashOnImportShadowing.ts new file mode 100644 index 00000000000..69e8b6a0f8c --- /dev/null +++ b/tests/cases/compiler/noCrashOnImportShadowing.ts @@ -0,0 +1,25 @@ +// @filename: b.ts +export const zzz = 123; + +// @filename: a.ts +import * as B from "./b"; + +interface B { + x: string; +} + +const x: B = { x: "" }; +B.zzz; + +export { B }; + +// @filename: index.ts +import { B } from "./a"; + +const x: B = { x: "" }; +B.zzz; + +import * as OriginalB from "./b"; +OriginalB.zzz; + +const y: OriginalB = x; \ No newline at end of file From 616bb5fcf61bd9059147224b3aff3ec153fa0bd3 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Fri, 25 Aug 2017 07:10:53 -0700 Subject: [PATCH 15/26] Defer mapped type indexed access transformations --- src/compiler/checker.ts | 59 +++++++++++++++++------------------------ 1 file changed, 24 insertions(+), 35 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 4a07f349c9a..338af6b3ba3 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -5906,10 +5906,6 @@ namespace ts { } function getConstraintOfIndexedAccess(type: IndexedAccessType) { - const transformed = getTransformedIndexedAccessType(type); - if (transformed) { - return transformed; - } const baseObjectType = getBaseConstraintOfType(type.objectType); const baseIndexType = getBaseConstraintOfType(type.indexType); return baseObjectType || baseIndexType ? getIndexedAccessType(baseObjectType || type.objectType, baseIndexType || type.indexType) : undefined; @@ -7596,22 +7592,6 @@ namespace ts { return anyType; } - function getIndexedAccessForMappedType(type: MappedType, indexType: Type, accessNode?: ElementAccessExpression | IndexedAccessTypeNode) { - if (accessNode) { - // Check if the index type is assignable to 'keyof T' for the object type. - if (!isTypeAssignableTo(indexType, getIndexType(type))) { - error(accessNode, Diagnostics.Type_0_cannot_be_used_to_index_type_1, typeToString(indexType), typeToString(type)); - return unknownType; - } - if (accessNode.kind === SyntaxKind.ElementAccessExpression && isAssignmentTarget(accessNode) && type.declaration.readonlyToken) { - error(accessNode, Diagnostics.Index_signature_in_type_0_only_permits_reading, typeToString(type)); - } - } - const mapper = createTypeMapper([getTypeParameterFromMappedType(type)], [indexType]); - const templateMapper = type.mapper ? combineTypeMappers(type.mapper, mapper) : mapper; - return instantiateType(getTemplateTypeFromMappedType(type), templateMapper); - } - function isGenericObjectType(type: Type): boolean { return type.flags & TypeFlags.TypeVariable ? true : getObjectFlags(type) & ObjectFlags.Mapped ? isGenericIndexType(getConstraintTypeFromMappedType(type)) : @@ -7637,12 +7617,14 @@ namespace ts { return false; } - // Given an indexed access type T[K], if T is an intersection containing one or more generic types and one or - // more object types with only a string index signature, e.g. '(U & V & { [x: string]: D })[K]', return a - // transformed type of the form '(U & V)[K] | D'. This allows us to properly reason about higher order indexed - // access types with default property values as expressed by D. + // Transform an indexed access occurring in a read position to a simpler form. Return the simpler form, + // or undefined if no transformation is possible. function getTransformedIndexedAccessType(type: IndexedAccessType): Type { const objectType = type.objectType; + // Given an indexed access type T[K], if T is an intersection containing one or more generic types and one or + // more object types with only a string index signature, e.g. '(U & V & { [x: string]: D })[K]', return a + // transformed type of the form '(U & V)[K] | D'. This allows us to properly reason about higher order indexed + // access types with default property values as expressed by D. if (objectType.flags & TypeFlags.Intersection && isGenericObjectType(objectType) && some((objectType).types, isStringIndexOnlyType)) { const regularTypes: Type[] = []; const stringIndexTypes: Type[] = []; @@ -7659,20 +7641,23 @@ namespace ts { getIntersectionType(stringIndexTypes) ]); } + // If the object type is a mapped type { [P in K]: E }, where K is generic, instantiate E using a mapper + // that substitutes the index type for P. For example, for an index access { [P in K]: Box }[X], we + // construct the type Box. + if (isGenericMappedType(objectType)) { + const mapper = createTypeMapper([getTypeParameterFromMappedType(objectType)], [type.indexType]); + const objectTypeMapper = (objectType).mapper; + const templateMapper = objectTypeMapper ? combineTypeMappers(objectTypeMapper, mapper) : mapper; + return instantiateType(getTemplateTypeFromMappedType(objectType), templateMapper); + } return undefined; } function getIndexedAccessType(objectType: Type, indexType: Type, accessNode?: ElementAccessExpression | IndexedAccessTypeNode): Type { - // If the object type is a mapped type { [P in K]: E }, where K is generic, we instantiate E using a mapper - // that substitutes the index type for P. For example, for an index access { [P in K]: Box }[X], we - // construct the type Box. - if (isGenericMappedType(objectType)) { - return getIndexedAccessForMappedType(objectType, indexType, accessNode); - } - // Otherwise, if the index type is generic, or if the object type is generic and doesn't originate in an - // expression, we are performing a higher-order index access where we cannot meaningfully access the properties - // of the object type. Note that for a generic T and a non-generic K, we eagerly resolve T[K] if it originates - // in an expression. This is to preserve backwards compatibility. For example, an element access 'this["foo"]' + // If the index type is generic, or if the object type is generic and doesn't originate in an expression, + // we are performing a higher-order index access where we cannot meaningfully access the properties of the + // object type. Note that for a generic T and a non-generic K, we eagerly resolve T[K] if it originates in + // an expression. This is to preserve backwards compatibility. For example, an element access 'this["foo"]' // has always been resolved eagerly using the constraint type of 'this' at the given location. if (isGenericIndexType(indexType) || !(accessNode && accessNode.kind === SyntaxKind.ElementAccessExpression) && isGenericObjectType(objectType)) { if (objectType.flags & TypeFlags.Any) { @@ -9334,7 +9319,7 @@ namespace ts { else if (source.flags & TypeFlags.IndexedAccess) { // A type S[K] is related to a type T if A[K] is related to T, where K is string-like and // A is the apparent type of S. - const constraint = getConstraintOfType(source); + const constraint = getTransformedIndexedAccessType(source) || getConstraintOfIndexedAccess(source); if (constraint) { if (result = isRelatedTo(constraint, target, reportErrors)) { errorInfo = saveErrorInfo; @@ -18810,6 +18795,10 @@ namespace ts { const objectType = (type).objectType; const indexType = (type).indexType; if (isTypeAssignableTo(indexType, getIndexType(objectType))) { + if (accessNode.kind === SyntaxKind.ElementAccessExpression && isAssignmentTarget(accessNode) && + getObjectFlags(objectType) & ObjectFlags.Mapped && (objectType).declaration.readonlyToken) { + error(accessNode, Diagnostics.Index_signature_in_type_0_only_permits_reading, typeToString(objectType)); + } return type; } // Check if we're indexing with a numeric type and if either object or index types From 3d3ed04b2868f5b74a6326f1eec8988e98309aca Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Fri, 25 Aug 2017 08:49:34 -0700 Subject: [PATCH 16/26] Perform indexed access type transformations consistently --- src/compiler/checker.ts | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 338af6b3ba3..4d08292a9f2 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -5906,6 +5906,10 @@ namespace ts { } function getConstraintOfIndexedAccess(type: IndexedAccessType) { + const transformed = getTransformedIndexedAccessType(type); + if (transformed) { + return transformed; + } const baseObjectType = getBaseConstraintOfType(type.objectType); const baseIndexType = getBaseConstraintOfType(type.indexType); return baseObjectType || baseIndexType ? getIndexedAccessType(baseObjectType || type.objectType, baseIndexType || type.indexType) : undefined; @@ -7617,8 +7621,8 @@ namespace ts { return false; } - // Transform an indexed access occurring in a read position to a simpler form. Return the simpler form, - // or undefined if no transformation is possible. + // Transform an indexed access to a simpler form, if possible. Return the simpler form, or return + // undefined if no transformation is possible. function getTransformedIndexedAccessType(type: IndexedAccessType): Type { const objectType = type.objectType; // Given an indexed access type T[K], if T is an intersection containing one or more generic types and one or @@ -9279,7 +9283,7 @@ namespace ts { else if (target.flags & TypeFlags.IndexedAccess) { // A type S is related to a type T[K] if S is related to A[K], where K is string-like and // A is the apparent type of S. - const constraint = getConstraintOfType(target); + const constraint = getConstraintOfIndexedAccess(target); if (constraint) { if (result = isRelatedTo(source, constraint, reportErrors)) { errorInfo = saveErrorInfo; @@ -9319,7 +9323,7 @@ namespace ts { else if (source.flags & TypeFlags.IndexedAccess) { // A type S[K] is related to a type T if A[K] is related to T, where K is string-like and // A is the apparent type of S. - const constraint = getTransformedIndexedAccessType(source) || getConstraintOfIndexedAccess(source); + const constraint = getConstraintOfIndexedAccess(source); if (constraint) { if (result = isRelatedTo(constraint, target, reportErrors)) { errorInfo = saveErrorInfo; From cf998bf35095674a3cd7aa85b020d66ad5e3d7b8 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Fri, 25 Aug 2017 08:49:59 -0700 Subject: [PATCH 17/26] Accept new baselines --- .../isomorphicMappedTypeInference.types | 6 +- tests/baselines/reference/keyofAndForIn.types | 24 +- .../reference/keyofAndIndexedAccess.types | 2 +- .../mappedTypeRelationships.errors.txt | 206 +++++++++++------- tests/baselines/reference/mappedTypes4.types | 2 +- .../reference/typeGuardsTypeParameters.types | 8 +- 6 files changed, 154 insertions(+), 94 deletions(-) diff --git a/tests/baselines/reference/isomorphicMappedTypeInference.types b/tests/baselines/reference/isomorphicMappedTypeInference.types index c3414f46017..2e13f69c5eb 100644 --- a/tests/baselines/reference/isomorphicMappedTypeInference.types +++ b/tests/baselines/reference/isomorphicMappedTypeInference.types @@ -69,7 +69,7 @@ function boxify(obj: T): Boxified { result[k] = box(obj[k]); >result[k] = box(obj[k]) : Box ->result[k] : Box +>result[k] : Boxified[keyof T] >result : Boxified >k : keyof T >box(obj[k]) : Box @@ -107,7 +107,7 @@ function unboxify(obj: Boxified): T { >k : keyof T >unbox(obj[k]) : T[keyof T] >unbox : (x: Box) => T ->obj[k] : Box +>obj[k] : Boxified[keyof T] >obj : Boxified >k : keyof T } @@ -131,7 +131,7 @@ function assignBoxified(obj: Boxified, values: T) { obj[k].value = values[k]; >obj[k].value = values[k] : T[keyof T] >obj[k].value : T[keyof T] ->obj[k] : Box +>obj[k] : Boxified[keyof T] >obj : Boxified >k : keyof T >value : T[keyof T] diff --git a/tests/baselines/reference/keyofAndForIn.types b/tests/baselines/reference/keyofAndForIn.types index d20f1a57070..c4997c05bfe 100644 --- a/tests/baselines/reference/keyofAndForIn.types +++ b/tests/baselines/reference/keyofAndForIn.types @@ -27,8 +27,8 @@ function f1(obj: { [P in K]: T }, k: K) { >obj : { [P in K]: T; } let x1 = obj[k1]; ->x1 : T ->obj[k1] : T +>x1 : { [P in K]: T; }[K] +>obj[k1] : { [P in K]: T; }[K] >obj : { [P in K]: T; } >k1 : K } @@ -37,8 +37,8 @@ function f1(obj: { [P in K]: T }, k: K) { >obj : { [P in K]: T; } let x2 = obj[k2]; ->x2 : T ->obj[k2] : T +>x2 : { [P in K]: T; }[K] +>obj[k2] : { [P in K]: T; }[K] >obj : { [P in K]: T; } >k2 : K } @@ -70,8 +70,8 @@ function f2(obj: { [P in keyof T]: T[P] }, k: keyof T) { >obj : { [P in keyof T]: T[P]; } let x1 = obj[k1]; ->x1 : T[keyof T] ->obj[k1] : T[keyof T] +>x1 : { [P in keyof T]: T[P]; }[keyof T] +>obj[k1] : { [P in keyof T]: T[P]; }[keyof T] >obj : { [P in keyof T]: T[P]; } >k1 : keyof T } @@ -80,8 +80,8 @@ function f2(obj: { [P in keyof T]: T[P] }, k: keyof T) { >obj : { [P in keyof T]: T[P]; } let x2 = obj[k2]; ->x2 : T[keyof T] ->obj[k2] : T[keyof T] +>x2 : { [P in keyof T]: T[P]; }[keyof T] +>obj[k2] : { [P in keyof T]: T[P]; }[keyof T] >obj : { [P in keyof T]: T[P]; } >k2 : keyof T } @@ -115,8 +115,8 @@ function f3(obj: { [P in K]: T[P] }, k: K) { >obj : { [P in K]: T[P]; } let x1 = obj[k1]; ->x1 : T[K] ->obj[k1] : T[K] +>x1 : { [P in K]: T[P]; }[K] +>obj[k1] : { [P in K]: T[P]; }[K] >obj : { [P in K]: T[P]; } >k1 : K } @@ -125,8 +125,8 @@ function f3(obj: { [P in K]: T[P] }, k: K) { >obj : { [P in K]: T[P]; } let x2 = obj[k2]; ->x2 : T[K] ->obj[k2] : T[K] +>x2 : { [P in K]: T[P]; }[K] +>obj[k2] : { [P in K]: T[P]; }[K] >obj : { [P in K]: T[P]; } >k2 : K } diff --git a/tests/baselines/reference/keyofAndIndexedAccess.types b/tests/baselines/reference/keyofAndIndexedAccess.types index 671289527aa..25d79e3f4ff 100644 --- a/tests/baselines/reference/keyofAndIndexedAccess.types +++ b/tests/baselines/reference/keyofAndIndexedAccess.types @@ -2194,7 +2194,7 @@ class Form { this.childFormFactories[prop](value) >this.childFormFactories[prop](value) : Form ->this.childFormFactories[prop] : (v: T[K]) => Form +>this.childFormFactories[prop] : { [K in keyof T]: (v: T[K]) => Form; }[K] >this.childFormFactories : { [K in keyof T]: (v: T[K]) => Form; } >this : this >childFormFactories : { [K in keyof T]: (v: T[K]) => Form; } diff --git a/tests/baselines/reference/mappedTypeRelationships.errors.txt b/tests/baselines/reference/mappedTypeRelationships.errors.txt index a1e1455b419..1ea0c5a2612 100644 --- a/tests/baselines/reference/mappedTypeRelationships.errors.txt +++ b/tests/baselines/reference/mappedTypeRelationships.errors.txt @@ -26,17 +26,64 @@ tests/cases/conformance/types/mapped/mappedTypeRelationships.ts(26,5): error TS2 Type 'T[string]' is not assignable to type 'U[string]'. Type 'T' is not assignable to type 'U'. tests/cases/conformance/types/mapped/mappedTypeRelationships.ts(26,12): error TS2536: Type 'K' cannot be used to index type 'T'. -tests/cases/conformance/types/mapped/mappedTypeRelationships.ts(30,5): error TS2322: Type 'T[keyof T] | undefined' is not assignable to type 'T[keyof T]'. - Type 'undefined' is not assignable to type 'T[keyof T]'. - Type 'undefined' is not assignable to type 'T[string]'. -tests/cases/conformance/types/mapped/mappedTypeRelationships.ts(35,5): error TS2322: Type 'T[K] | undefined' is not assignable to type 'T[K]'. - Type 'undefined' is not assignable to type 'T[K]'. - Type 'undefined' is not assignable to type 'T[string]'. -tests/cases/conformance/types/mapped/mappedTypeRelationships.ts(40,5): error TS2322: Type 'U[keyof T] | undefined' is not assignable to type 'T[keyof T]'. - Type 'undefined' is not assignable to type 'T[keyof T]'. - Type 'undefined' is not assignable to type 'T[string]'. -tests/cases/conformance/types/mapped/mappedTypeRelationships.ts(41,5): error TS2322: Type 'T[keyof T]' is not assignable to type 'U[keyof T] | undefined'. - Type 'T[string]' is not assignable to type 'U[keyof T] | undefined'. +tests/cases/conformance/types/mapped/mappedTypeRelationships.ts(30,5): error TS2322: Type 'Partial[keyof T]' is not assignable to type 'T[keyof T]'. + Type 'T[keyof T] | undefined' is not assignable to type 'T[keyof T]'. + Type 'undefined' is not assignable to type 'T[keyof T]'. + Type 'undefined' is not assignable to type 'T[string]'. + Type 'Partial[keyof T]' is not assignable to type 'T[string]'. + Type 'T[keyof T] | undefined' is not assignable to type 'T[string]'. + Type 'undefined' is not assignable to type 'T[string]'. +tests/cases/conformance/types/mapped/mappedTypeRelationships.ts(35,5): error TS2322: Type 'Partial[K]' is not assignable to type 'T[K]'. + Type 'T[K] | undefined' is not assignable to type 'T[K]'. + Type 'undefined' is not assignable to type 'T[K]'. + Type 'undefined' is not assignable to type 'T[string]'. + Type 'Partial[K]' is not assignable to type 'T[string]'. + Type 'T[K] | undefined' is not assignable to type 'T[string]'. + Type 'undefined' is not assignable to type 'T[string]'. +tests/cases/conformance/types/mapped/mappedTypeRelationships.ts(40,5): error TS2322: Type 'Partial[keyof T]' is not assignable to type 'T[keyof T]'. + Type 'U[keyof T] | undefined' is not assignable to type 'T[keyof T]'. + Type 'undefined' is not assignable to type 'T[keyof T]'. + Type 'undefined' is not assignable to type 'T[string]'. + Type 'Partial[keyof T]' is not assignable to type 'T[string]'. + Type 'U[keyof T] | undefined' is not assignable to type 'T[string]'. + Type 'undefined' is not assignable to type 'T[string]'. +tests/cases/conformance/types/mapped/mappedTypeRelationships.ts(41,5): error TS2322: Type 'T[keyof T]' is not assignable to type 'Partial[keyof T]'. + Type 'T[string]' is not assignable to type 'Partial[keyof T]'. + Type 'T[string]' is not assignable to type 'U[keyof T] | undefined'. + Type 'T[string]' is not assignable to type 'U[keyof T]'. + Type 'T[keyof T]' is not assignable to type 'U[keyof T] | undefined'. + Type 'T[string]' is not assignable to type 'U[keyof T] | undefined'. + Type 'T[string]' is not assignable to type 'U[keyof T]'. + Type 'T[keyof T]' is not assignable to type 'U[keyof T]'. + Type 'T[string]' is not assignable to type 'U[keyof T]'. + Type 'T[string]' is not assignable to type 'U[string]'. + Type 'T[keyof T]' is not assignable to type 'U[string]'. + Type 'T[string]' is not assignable to type 'U[string]'. + Type 'T' is not assignable to type 'U'. +tests/cases/conformance/types/mapped/mappedTypeRelationships.ts(45,5): error TS2322: Type 'Partial[K]' is not assignable to type 'T[K]'. + Type 'U[K] | undefined' is not assignable to type 'T[K]'. + Type 'undefined' is not assignable to type 'T[K]'. + Type 'undefined' is not assignable to type 'T[string]'. + Type 'Partial[K]' is not assignable to type 'T[string]'. + Type 'U[K] | undefined' is not assignable to type 'T[string]'. + Type 'undefined' is not assignable to type 'T[string]'. +tests/cases/conformance/types/mapped/mappedTypeRelationships.ts(46,5): error TS2322: Type 'T[K]' is not assignable to type 'Partial[K]'. + Type 'T[string]' is not assignable to type 'Partial[K]'. + Type 'T[string]' is not assignable to type 'U[K] | undefined'. + Type 'T[string]' is not assignable to type 'U[K]'. + Type 'T[K]' is not assignable to type 'U[K] | undefined'. + Type 'T[string]' is not assignable to type 'U[K] | undefined'. + Type 'T[string]' is not assignable to type 'U[K]'. + Type 'T[K]' is not assignable to type 'U[K]'. + Type 'T[string]' is not assignable to type 'U[K]'. + Type 'T[string]' is not assignable to type 'U[string]'. + Type 'T[K]' is not assignable to type 'U[string]'. + Type 'T[string]' is not assignable to type 'U[string]'. + Type 'T' is not assignable to type 'U'. +tests/cases/conformance/types/mapped/mappedTypeRelationships.ts(51,5): error TS2542: Index signature in type 'Readonly' only permits reading. +tests/cases/conformance/types/mapped/mappedTypeRelationships.ts(56,5): error TS2542: Index signature in type 'Readonly' only permits reading. +tests/cases/conformance/types/mapped/mappedTypeRelationships.ts(61,5): error TS2322: Type 'T[keyof T]' is not assignable to type 'Readonly[keyof T]'. + Type 'T[string]' is not assignable to type 'Readonly[keyof T]'. Type 'T[string]' is not assignable to type 'U[keyof T]'. Type 'T[keyof T]' is not assignable to type 'U[keyof T]'. Type 'T[string]' is not assignable to type 'U[keyof T]'. @@ -44,11 +91,9 @@ tests/cases/conformance/types/mapped/mappedTypeRelationships.ts(41,5): error TS2 Type 'T[keyof T]' is not assignable to type 'U[string]'. Type 'T[string]' is not assignable to type 'U[string]'. Type 'T' is not assignable to type 'U'. -tests/cases/conformance/types/mapped/mappedTypeRelationships.ts(45,5): error TS2322: Type 'U[K] | undefined' is not assignable to type 'T[K]'. - Type 'undefined' is not assignable to type 'T[K]'. - Type 'undefined' is not assignable to type 'T[string]'. -tests/cases/conformance/types/mapped/mappedTypeRelationships.ts(46,5): error TS2322: Type 'T[K]' is not assignable to type 'U[K] | undefined'. - Type 'T[string]' is not assignable to type 'U[K] | undefined'. +tests/cases/conformance/types/mapped/mappedTypeRelationships.ts(61,5): error TS2542: Index signature in type 'Readonly' only permits reading. +tests/cases/conformance/types/mapped/mappedTypeRelationships.ts(66,5): error TS2322: Type 'T[K]' is not assignable to type 'Readonly[K]'. + Type 'T[string]' is not assignable to type 'Readonly[K]'. Type 'T[string]' is not assignable to type 'U[K]'. Type 'T[K]' is not assignable to type 'U[K]'. Type 'T[string]' is not assignable to type 'U[K]'. @@ -56,21 +101,6 @@ tests/cases/conformance/types/mapped/mappedTypeRelationships.ts(46,5): error TS2 Type 'T[K]' is not assignable to type 'U[string]'. Type 'T[string]' is not assignable to type 'U[string]'. Type 'T' is not assignable to type 'U'. -tests/cases/conformance/types/mapped/mappedTypeRelationships.ts(51,5): error TS2542: Index signature in type 'Readonly' only permits reading. -tests/cases/conformance/types/mapped/mappedTypeRelationships.ts(56,5): error TS2542: Index signature in type 'Readonly' only permits reading. -tests/cases/conformance/types/mapped/mappedTypeRelationships.ts(61,5): error TS2322: Type 'T[keyof T]' is not assignable to type 'U[keyof T]'. - Type 'T[string]' is not assignable to type 'U[keyof T]'. - Type 'T[string]' is not assignable to type 'U[string]'. - Type 'T[keyof T]' is not assignable to type 'U[string]'. - Type 'T[string]' is not assignable to type 'U[string]'. - Type 'T' is not assignable to type 'U'. -tests/cases/conformance/types/mapped/mappedTypeRelationships.ts(61,5): error TS2542: Index signature in type 'Readonly' only permits reading. -tests/cases/conformance/types/mapped/mappedTypeRelationships.ts(66,5): error TS2322: Type 'T[K]' is not assignable to type 'U[K]'. - Type 'T[string]' is not assignable to type 'U[K]'. - Type 'T[string]' is not assignable to type 'U[string]'. - Type 'T[K]' is not assignable to type 'U[string]'. - Type 'T[string]' is not assignable to type 'U[string]'. - Type 'T' is not assignable to type 'U'. tests/cases/conformance/types/mapped/mappedTypeRelationships.ts(66,5): error TS2542: Index signature in type 'Readonly' only permits reading. tests/cases/conformance/types/mapped/mappedTypeRelationships.ts(72,5): error TS2322: Type 'Partial' is not assignable to type 'T'. tests/cases/conformance/types/mapped/mappedTypeRelationships.ts(78,5): error TS2322: Type 'Partial' is not assignable to type 'Partial'. @@ -168,57 +198,81 @@ tests/cases/conformance/types/mapped/mappedTypeRelationships.ts(168,5): error TS function f10(x: T, y: Partial, k: keyof T) { x[k] = y[k]; // Error ~~~~ -!!! error TS2322: Type 'T[keyof T] | undefined' is not assignable to type 'T[keyof T]'. -!!! error TS2322: Type 'undefined' is not assignable to type 'T[keyof T]'. -!!! error TS2322: Type 'undefined' is not assignable to type 'T[string]'. +!!! error TS2322: Type 'Partial[keyof T]' is not assignable to type 'T[keyof T]'. +!!! error TS2322: Type 'T[keyof T] | undefined' is not assignable to type 'T[keyof T]'. +!!! error TS2322: Type 'undefined' is not assignable to type 'T[keyof T]'. +!!! error TS2322: Type 'undefined' is not assignable to type 'T[string]'. +!!! error TS2322: Type 'Partial[keyof T]' is not assignable to type 'T[string]'. +!!! error TS2322: Type 'T[keyof T] | undefined' is not assignable to type 'T[string]'. +!!! error TS2322: Type 'undefined' is not assignable to type 'T[string]'. y[k] = x[k]; } function f11(x: T, y: Partial, k: K) { x[k] = y[k]; // Error ~~~~ -!!! error TS2322: Type 'T[K] | undefined' is not assignable to type 'T[K]'. -!!! error TS2322: Type 'undefined' is not assignable to type 'T[K]'. -!!! error TS2322: Type 'undefined' is not assignable to type 'T[string]'. +!!! error TS2322: Type 'Partial[K]' is not assignable to type 'T[K]'. +!!! error TS2322: Type 'T[K] | undefined' is not assignable to type 'T[K]'. +!!! error TS2322: Type 'undefined' is not assignable to type 'T[K]'. +!!! error TS2322: Type 'undefined' is not assignable to type 'T[string]'. +!!! error TS2322: Type 'Partial[K]' is not assignable to type 'T[string]'. +!!! error TS2322: Type 'T[K] | undefined' is not assignable to type 'T[string]'. +!!! error TS2322: Type 'undefined' is not assignable to type 'T[string]'. y[k] = x[k]; } function f12(x: T, y: Partial, k: keyof T) { x[k] = y[k]; // Error ~~~~ -!!! error TS2322: Type 'U[keyof T] | undefined' is not assignable to type 'T[keyof T]'. -!!! error TS2322: Type 'undefined' is not assignable to type 'T[keyof T]'. -!!! error TS2322: Type 'undefined' is not assignable to type 'T[string]'. +!!! error TS2322: Type 'Partial[keyof T]' is not assignable to type 'T[keyof T]'. +!!! error TS2322: Type 'U[keyof T] | undefined' is not assignable to type 'T[keyof T]'. +!!! error TS2322: Type 'undefined' is not assignable to type 'T[keyof T]'. +!!! error TS2322: Type 'undefined' is not assignable to type 'T[string]'. +!!! error TS2322: Type 'Partial[keyof T]' is not assignable to type 'T[string]'. +!!! error TS2322: Type 'U[keyof T] | undefined' is not assignable to type 'T[string]'. +!!! error TS2322: Type 'undefined' is not assignable to type 'T[string]'. y[k] = x[k]; // Error ~~~~ -!!! error TS2322: Type 'T[keyof T]' is not assignable to type 'U[keyof T] | undefined'. -!!! error TS2322: Type 'T[string]' is not assignable to type 'U[keyof T] | undefined'. -!!! error TS2322: Type 'T[string]' is not assignable to type 'U[keyof T]'. -!!! error TS2322: Type 'T[keyof T]' is not assignable to type 'U[keyof T]'. -!!! error TS2322: Type 'T[string]' is not assignable to type 'U[keyof T]'. -!!! error TS2322: Type 'T[string]' is not assignable to type 'U[string]'. -!!! error TS2322: Type 'T[keyof T]' is not assignable to type 'U[string]'. -!!! error TS2322: Type 'T[string]' is not assignable to type 'U[string]'. -!!! error TS2322: Type 'T' is not assignable to type 'U'. +!!! error TS2322: Type 'T[keyof T]' is not assignable to type 'Partial[keyof T]'. +!!! error TS2322: Type 'T[string]' is not assignable to type 'Partial[keyof T]'. +!!! error TS2322: Type 'T[string]' is not assignable to type 'U[keyof T] | undefined'. +!!! error TS2322: Type 'T[string]' is not assignable to type 'U[keyof T]'. +!!! error TS2322: Type 'T[keyof T]' is not assignable to type 'U[keyof T] | undefined'. +!!! error TS2322: Type 'T[string]' is not assignable to type 'U[keyof T] | undefined'. +!!! error TS2322: Type 'T[string]' is not assignable to type 'U[keyof T]'. +!!! error TS2322: Type 'T[keyof T]' is not assignable to type 'U[keyof T]'. +!!! error TS2322: Type 'T[string]' is not assignable to type 'U[keyof T]'. +!!! error TS2322: Type 'T[string]' is not assignable to type 'U[string]'. +!!! error TS2322: Type 'T[keyof T]' is not assignable to type 'U[string]'. +!!! error TS2322: Type 'T[string]' is not assignable to type 'U[string]'. +!!! error TS2322: Type 'T' is not assignable to type 'U'. } function f13(x: T, y: Partial, k: K) { x[k] = y[k]; // Error ~~~~ -!!! error TS2322: Type 'U[K] | undefined' is not assignable to type 'T[K]'. -!!! error TS2322: Type 'undefined' is not assignable to type 'T[K]'. -!!! error TS2322: Type 'undefined' is not assignable to type 'T[string]'. +!!! error TS2322: Type 'Partial[K]' is not assignable to type 'T[K]'. +!!! error TS2322: Type 'U[K] | undefined' is not assignable to type 'T[K]'. +!!! error TS2322: Type 'undefined' is not assignable to type 'T[K]'. +!!! error TS2322: Type 'undefined' is not assignable to type 'T[string]'. +!!! error TS2322: Type 'Partial[K]' is not assignable to type 'T[string]'. +!!! error TS2322: Type 'U[K] | undefined' is not assignable to type 'T[string]'. +!!! error TS2322: Type 'undefined' is not assignable to type 'T[string]'. y[k] = x[k]; // Error ~~~~ -!!! error TS2322: Type 'T[K]' is not assignable to type 'U[K] | undefined'. -!!! error TS2322: Type 'T[string]' is not assignable to type 'U[K] | undefined'. -!!! error TS2322: Type 'T[string]' is not assignable to type 'U[K]'. -!!! error TS2322: Type 'T[K]' is not assignable to type 'U[K]'. -!!! error TS2322: Type 'T[string]' is not assignable to type 'U[K]'. -!!! error TS2322: Type 'T[string]' is not assignable to type 'U[string]'. -!!! error TS2322: Type 'T[K]' is not assignable to type 'U[string]'. -!!! error TS2322: Type 'T[string]' is not assignable to type 'U[string]'. -!!! error TS2322: Type 'T' is not assignable to type 'U'. +!!! error TS2322: Type 'T[K]' is not assignable to type 'Partial[K]'. +!!! error TS2322: Type 'T[string]' is not assignable to type 'Partial[K]'. +!!! error TS2322: Type 'T[string]' is not assignable to type 'U[K] | undefined'. +!!! error TS2322: Type 'T[string]' is not assignable to type 'U[K]'. +!!! error TS2322: Type 'T[K]' is not assignable to type 'U[K] | undefined'. +!!! error TS2322: Type 'T[string]' is not assignable to type 'U[K] | undefined'. +!!! error TS2322: Type 'T[string]' is not assignable to type 'U[K]'. +!!! error TS2322: Type 'T[K]' is not assignable to type 'U[K]'. +!!! error TS2322: Type 'T[string]' is not assignable to type 'U[K]'. +!!! error TS2322: Type 'T[string]' is not assignable to type 'U[string]'. +!!! error TS2322: Type 'T[K]' is not assignable to type 'U[string]'. +!!! error TS2322: Type 'T[string]' is not assignable to type 'U[string]'. +!!! error TS2322: Type 'T' is not assignable to type 'U'. } function f20(x: T, y: Readonly, k: keyof T) { @@ -239,12 +293,15 @@ tests/cases/conformance/types/mapped/mappedTypeRelationships.ts(168,5): error TS x[k] = y[k]; y[k] = x[k]; // Error ~~~~ -!!! error TS2322: Type 'T[keyof T]' is not assignable to type 'U[keyof T]'. -!!! error TS2322: Type 'T[string]' is not assignable to type 'U[keyof T]'. -!!! error TS2322: Type 'T[string]' is not assignable to type 'U[string]'. -!!! error TS2322: Type 'T[keyof T]' is not assignable to type 'U[string]'. -!!! error TS2322: Type 'T[string]' is not assignable to type 'U[string]'. -!!! error TS2322: Type 'T' is not assignable to type 'U'. +!!! error TS2322: Type 'T[keyof T]' is not assignable to type 'Readonly[keyof T]'. +!!! error TS2322: Type 'T[string]' is not assignable to type 'Readonly[keyof T]'. +!!! error TS2322: Type 'T[string]' is not assignable to type 'U[keyof T]'. +!!! error TS2322: Type 'T[keyof T]' is not assignable to type 'U[keyof T]'. +!!! error TS2322: Type 'T[string]' is not assignable to type 'U[keyof T]'. +!!! error TS2322: Type 'T[string]' is not assignable to type 'U[string]'. +!!! error TS2322: Type 'T[keyof T]' is not assignable to type 'U[string]'. +!!! error TS2322: Type 'T[string]' is not assignable to type 'U[string]'. +!!! error TS2322: Type 'T' is not assignable to type 'U'. ~~~~ !!! error TS2542: Index signature in type 'Readonly' only permits reading. } @@ -253,12 +310,15 @@ tests/cases/conformance/types/mapped/mappedTypeRelationships.ts(168,5): error TS x[k] = y[k]; y[k] = x[k]; // Error ~~~~ -!!! error TS2322: Type 'T[K]' is not assignable to type 'U[K]'. -!!! error TS2322: Type 'T[string]' is not assignable to type 'U[K]'. -!!! error TS2322: Type 'T[string]' is not assignable to type 'U[string]'. -!!! error TS2322: Type 'T[K]' is not assignable to type 'U[string]'. -!!! error TS2322: Type 'T[string]' is not assignable to type 'U[string]'. -!!! error TS2322: Type 'T' is not assignable to type 'U'. +!!! error TS2322: Type 'T[K]' is not assignable to type 'Readonly[K]'. +!!! error TS2322: Type 'T[string]' is not assignable to type 'Readonly[K]'. +!!! error TS2322: Type 'T[string]' is not assignable to type 'U[K]'. +!!! error TS2322: Type 'T[K]' is not assignable to type 'U[K]'. +!!! error TS2322: Type 'T[string]' is not assignable to type 'U[K]'. +!!! error TS2322: Type 'T[string]' is not assignable to type 'U[string]'. +!!! error TS2322: Type 'T[K]' is not assignable to type 'U[string]'. +!!! error TS2322: Type 'T[string]' is not assignable to type 'U[string]'. +!!! error TS2322: Type 'T' is not assignable to type 'U'. ~~~~ !!! error TS2542: Index signature in type 'Readonly' only permits reading. } diff --git a/tests/baselines/reference/mappedTypes4.types b/tests/baselines/reference/mappedTypes4.types index 30e7eb2431b..c003765d517 100644 --- a/tests/baselines/reference/mappedTypes4.types +++ b/tests/baselines/reference/mappedTypes4.types @@ -45,7 +45,7 @@ function boxify(obj: T): Boxified { result[k] = { value: obj[k] }; >result[k] = { value: obj[k] } : { value: T[keyof T]; } ->result[k] : Box +>result[k] : Boxified[keyof T] >result : Boxified >k : keyof T >{ value: obj[k] } : { value: T[keyof T]; } diff --git a/tests/baselines/reference/typeGuardsTypeParameters.types b/tests/baselines/reference/typeGuardsTypeParameters.types index 82a52a46b86..13fb9965235 100644 --- a/tests/baselines/reference/typeGuardsTypeParameters.types +++ b/tests/baselines/reference/typeGuardsTypeParameters.types @@ -84,15 +84,15 @@ function fun(item: { [P in keyof T]: T[P] }) { >item : { [P in keyof T]: T[P]; } const value = item[key]; ->value : T[keyof T] ->item[key] : T[keyof T] +>value : { [P in keyof T]: T[P]; }[keyof T] +>item[key] : { [P in keyof T]: T[P]; }[keyof T] >item : { [P in keyof T]: T[P]; } >key : keyof T if (typeof value === "string") { >typeof value === "string" : boolean >typeof value : "string" | "number" | "boolean" | "symbol" | "undefined" | "object" | "function" ->value : T[keyof T] +>value : { [P in keyof T]: T[P]; }[keyof T] >"string" : "string" strings.push(value); @@ -100,7 +100,7 @@ function fun(item: { [P in keyof T]: T[P] }) { >strings.push : (...items: string[]) => number >strings : string[] >push : (...items: string[]) => number ->value : T[keyof T] & string +>value : { [P in keyof T]: T[P]; }[keyof T] & string } } } From e79d75a383f7d540eba2859ea4a5a1fb5f80e9e5 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Fri, 25 Aug 2017 08:55:43 -0700 Subject: [PATCH 18/26] Add regression test --- .../mappedTypeIndexedAccess.errors.txt | 49 +++++++++++++++++++ .../reference/mappedTypeIndexedAccess.js | 43 ++++++++++++++++ .../cases/compiler/mappedTypeIndexedAccess.ts | 29 +++++++++++ 3 files changed, 121 insertions(+) create mode 100644 tests/baselines/reference/mappedTypeIndexedAccess.errors.txt create mode 100644 tests/baselines/reference/mappedTypeIndexedAccess.js create mode 100644 tests/cases/compiler/mappedTypeIndexedAccess.ts diff --git a/tests/baselines/reference/mappedTypeIndexedAccess.errors.txt b/tests/baselines/reference/mappedTypeIndexedAccess.errors.txt new file mode 100644 index 00000000000..24eae941c61 --- /dev/null +++ b/tests/baselines/reference/mappedTypeIndexedAccess.errors.txt @@ -0,0 +1,49 @@ +tests/cases/compiler/mappedTypeIndexedAccess.ts(18,5): error TS2322: Type '{ key: "foo"; value: number; }' is not assignable to type '{ key: "foo"; value: string; } | { key: "bar"; value: number; }'. + Type '{ key: "foo"; value: number; }' is not assignable to type '{ key: "foo"; value: string; }'. + Types of property 'value' are incompatible. + Type 'number' is not assignable to type 'string'. +tests/cases/compiler/mappedTypeIndexedAccess.ts(24,5): error TS2322: Type '{ key: "foo"; value: number; }' is not assignable to type '{ key: "foo"; value: string; } | { key: "bar"; value: number; }'. + Type '{ key: "foo"; value: number; }' is not assignable to type '{ key: "foo"; value: string; }'. + Types of property 'value' are incompatible. + Type 'number' is not assignable to type 'string'. + + +==== tests/cases/compiler/mappedTypeIndexedAccess.ts (2 errors) ==== + // Repro from #15756 + + type Pairs = { + [TKey in keyof T]: { + key: TKey; + value: T[TKey]; + }; + }; + + type Pair = Pairs[keyof T]; + + type FooBar = { + foo: string; + bar: number; + }; + + // Error expected here + let pair1: Pair = { + ~~~~~ +!!! error TS2322: Type '{ key: "foo"; value: number; }' is not assignable to type '{ key: "foo"; value: string; } | { key: "bar"; value: number; }'. +!!! error TS2322: Type '{ key: "foo"; value: number; }' is not assignable to type '{ key: "foo"; value: string; }'. +!!! error TS2322: Types of property 'value' are incompatible. +!!! error TS2322: Type 'number' is not assignable to type 'string'. + key: "foo", + value: 3 + }; + + // Error expected here + let pair2: Pairs[keyof FooBar] = { + ~~~~~ +!!! error TS2322: Type '{ key: "foo"; value: number; }' is not assignable to type '{ key: "foo"; value: string; } | { key: "bar"; value: number; }'. +!!! error TS2322: Type '{ key: "foo"; value: number; }' is not assignable to type '{ key: "foo"; value: string; }'. +!!! error TS2322: Types of property 'value' are incompatible. +!!! error TS2322: Type 'number' is not assignable to type 'string'. + key: "foo", + value: 3 + }; + \ No newline at end of file diff --git a/tests/baselines/reference/mappedTypeIndexedAccess.js b/tests/baselines/reference/mappedTypeIndexedAccess.js new file mode 100644 index 00000000000..60af082a6b9 --- /dev/null +++ b/tests/baselines/reference/mappedTypeIndexedAccess.js @@ -0,0 +1,43 @@ +//// [mappedTypeIndexedAccess.ts] +// Repro from #15756 + +type Pairs = { + [TKey in keyof T]: { + key: TKey; + value: T[TKey]; + }; +}; + +type Pair = Pairs[keyof T]; + +type FooBar = { + foo: string; + bar: number; +}; + +// Error expected here +let pair1: Pair = { + key: "foo", + value: 3 +}; + +// Error expected here +let pair2: Pairs[keyof FooBar] = { + key: "foo", + value: 3 +}; + + +//// [mappedTypeIndexedAccess.js] +"use strict"; +// Repro from #15756 +// Error expected here +var pair1 = { + key: "foo", + value: 3 +}; +// Error expected here +var pair2 = { + key: "foo", + value: 3 +}; diff --git a/tests/cases/compiler/mappedTypeIndexedAccess.ts b/tests/cases/compiler/mappedTypeIndexedAccess.ts new file mode 100644 index 00000000000..5be08a96f63 --- /dev/null +++ b/tests/cases/compiler/mappedTypeIndexedAccess.ts @@ -0,0 +1,29 @@ +// @strict: true + +// Repro from #15756 + +type Pairs = { + [TKey in keyof T]: { + key: TKey; + value: T[TKey]; + }; +}; + +type Pair = Pairs[keyof T]; + +type FooBar = { + foo: string; + bar: number; +}; + +// Error expected here +let pair1: Pair = { + key: "foo", + value: 3 +}; + +// Error expected here +let pair2: Pairs[keyof FooBar] = { + key: "foo", + value: 3 +}; From 3a0ab74ed6d5201ac703089f72915d23ef15242e Mon Sep 17 00:00:00 2001 From: Andy Date: Fri, 25 Aug 2017 09:53:28 -0700 Subject: [PATCH 19/26] Test for action description of code actions, and simplify description for extracting method to file (#18030) * Test for action description of code actions, and simplify description for extracting method to file * Add unit test file missing from tsconfig.json (only affects gulp) and update tests * Use the actual number * Use "module scope" or "global scope" instead of "this file" --- src/compiler/diagnosticMessages.json | 2 +- src/harness/fourslash.ts | 47 +++++++++++++------ src/harness/tsconfig.json | 1 + src/services/refactors/extractMethod.ts | 18 +++---- .../reference/extractMethod/extractMethod1.js | 8 ++-- .../extractMethod/extractMethod10.js | 6 +-- .../extractMethod/extractMethod11.js | 6 +-- .../extractMethod/extractMethod12.js | 2 +- .../reference/extractMethod/extractMethod2.js | 8 ++-- .../reference/extractMethod/extractMethod3.js | 8 ++-- .../reference/extractMethod/extractMethod4.js | 8 ++-- .../reference/extractMethod/extractMethod5.js | 8 ++-- .../reference/extractMethod/extractMethod6.js | 8 ++-- .../reference/extractMethod/extractMethod7.js | 8 ++-- .../reference/extractMethod/extractMethod8.js | 8 ++-- .../reference/extractMethod/extractMethod9.js | 8 ++-- tests/cases/fourslash/extract-method1.ts | 7 ++- tests/cases/fourslash/extract-method10.ts | 9 +++- tests/cases/fourslash/extract-method13.ts | 12 ++++- tests/cases/fourslash/extract-method14.ts | 6 ++- tests/cases/fourslash/extract-method15.ts | 6 ++- tests/cases/fourslash/extract-method18.ts | 7 ++- tests/cases/fourslash/extract-method19.ts | 9 ++-- tests/cases/fourslash/extract-method2.ts | 7 ++- tests/cases/fourslash/extract-method21.ts | 6 ++- tests/cases/fourslash/extract-method24.ts | 6 ++- tests/cases/fourslash/extract-method25.ts | 6 ++- tests/cases/fourslash/extract-method3.ts | 2 +- tests/cases/fourslash/extract-method5.ts | 6 ++- tests/cases/fourslash/extract-method7.ts | 6 ++- tests/cases/fourslash/fourslash.ts | 4 +- 31 files changed, 163 insertions(+), 90 deletions(-) diff --git a/src/compiler/diagnosticMessages.json b/src/compiler/diagnosticMessages.json index 8121f036cea..9a3492e5c37 100644 --- a/src/compiler/diagnosticMessages.json +++ b/src/compiler/diagnosticMessages.json @@ -3696,7 +3696,7 @@ "code": 95003 }, - "Extract function into '{0}'": { + "Extract function into {0}": { "category": "Message", "code": 95004 } diff --git a/src/harness/fourslash.ts b/src/harness/fourslash.ts index ab33fa87997..3010fc53533 100644 --- a/src/harness/fourslash.ts +++ b/src/harness/fourslash.ts @@ -2761,20 +2761,25 @@ namespace FourSlash { }); } - public verifyRefactorAvailable(negative: boolean, name?: string, subName?: string) { + public verifyRefactorAvailable(negative: boolean, name: string, actionName?: string) { const selection = this.getSelection(); let refactors = this.languageService.getApplicableRefactors(this.activeFile.fileName, selection) || []; - if (name) { - refactors = refactors.filter(r => r.name === name && (subName === undefined || r.actions.some(a => a.name === subName))); - } + refactors = refactors.filter(r => r.name === name && (actionName === undefined || r.actions.some(a => a.name === actionName))); const isAvailable = refactors.length > 0; - if (negative && isAvailable) { - this.raiseError(`verifyApplicableRefactorAvailableForRange failed - expected no refactor but found some: ${refactors.map(r => r.name).join(", ")}`); + if (negative) { + if (isAvailable) { + this.raiseError(`verifyApplicableRefactorAvailableForRange failed - expected no refactor but found: ${refactors.map(r => r.name).join(", ")}`); + } } - else if (!negative && !isAvailable) { - this.raiseError(`verifyApplicableRefactorAvailableForRange failed - expected a refactor but found none.`); + else { + if (!isAvailable) { + this.raiseError(`verifyApplicableRefactorAvailableForRange failed - expected a refactor but found none.`); + } + if (refactors.length > 1) { + this.raiseError(`${refactors.length} available refactors both have name ${name} and action ${actionName}`); + } } } @@ -2794,14 +2799,22 @@ namespace FourSlash { } } - public applyRefactor(refactorName: string, actionName: string) { + public applyRefactor({ refactorName, actionName, actionDescription }: FourSlashInterface.ApplyRefactorOptions) { const range = this.getSelection(); const refactors = this.languageService.getApplicableRefactors(this.activeFile.fileName, range); - const refactor = ts.find(refactors, r => r.name === refactorName); + const refactor = refactors.find(r => r.name === refactorName); if (!refactor) { this.raiseError(`The expected refactor: ${refactorName} is not available at the marker location.`); } + const action = refactor.actions.find(a => a.name === actionName); + if (!action) { + this.raiseError(`The expected action: ${action} is not included in: ${refactor.actions.map(a => a.name)}`); + } + if (action.description !== actionDescription) { + this.raiseError(`Expected action description to be ${JSON.stringify(actionDescription)}, got: ${JSON.stringify(action.description)}`); + } + const editInfo = this.languageService.getEditsForRefactor(this.activeFile.fileName, this.formatCodeSettings, range, refactorName, actionName); for (const edit of editInfo.edits) { this.applyEdits(edit.fileName, edit.textChanges, /*isFormattingEdit*/ false); @@ -3682,8 +3695,8 @@ namespace FourSlashInterface { this.state.verifyApplicableRefactorAvailableForRange(this.negative); } - public refactorAvailable(name?: string, subName?: string) { - this.state.verifyRefactorAvailable(this.negative, name, subName); + public refactorAvailable(name: string, actionName?: string) { + this.state.verifyRefactorAvailable(this.negative, name, actionName); } } @@ -4081,8 +4094,8 @@ namespace FourSlashInterface { this.state.enableFormatting = false; } - public applyRefactor(refactorName: string, actionName: string) { - this.state.applyRefactor(refactorName, actionName); + public applyRefactor(options: ApplyRefactorOptions) { + this.state.applyRefactor(options); } } @@ -4295,4 +4308,10 @@ namespace FourSlashInterface { return { classificationType, text, textSpan }; } } + + export interface ApplyRefactorOptions { + refactorName: string; + actionName: string; + actionDescription: string; + } } diff --git a/src/harness/tsconfig.json b/src/harness/tsconfig.json index 9165e59cb0a..1469079bcaa 100644 --- a/src/harness/tsconfig.json +++ b/src/harness/tsconfig.json @@ -125,6 +125,7 @@ "./unittests/printer.ts", "./unittests/transform.ts", "./unittests/customTransforms.ts", + "./unittests/extractMethods.ts", "./unittests/textChanges.ts", "./unittests/telemetry.ts", "./unittests/programMissingFiles.ts" diff --git a/src/services/refactors/extractMethod.ts b/src/services/refactors/extractMethod.ts index 90e33041695..f32321aff2a 100644 --- a/src/services/refactors/extractMethod.ts +++ b/src/services/refactors/extractMethod.ts @@ -560,32 +560,32 @@ namespace ts.refactor.extractMethod { return "constructor"; case SyntaxKind.FunctionExpression: return scope.name - ? `function expression ${scope.name.getText()}` + ? `function expression ${scope.name.text}` : "anonymous function expression"; case SyntaxKind.FunctionDeclaration: - return `function ${scope.name.getText()}`; + return `function '${scope.name.text}'`; case SyntaxKind.ArrowFunction: return "arrow function"; case SyntaxKind.MethodDeclaration: - return `method ${scope.name.getText()}`; + return `method '${scope.name.getText()}`; case SyntaxKind.GetAccessor: - return `get ${scope.name.getText()}`; + return `'get ${scope.name.getText()}'`; case SyntaxKind.SetAccessor: - return `set ${scope.name.getText()}`; + return `'set ${scope.name.getText()}'`; } } else if (isModuleBlock(scope)) { - return `namespace ${scope.parent.name.getText()}`; + return `namespace '${scope.parent.name.getText()}'`; } else if (isClassLike(scope)) { return scope.kind === SyntaxKind.ClassDeclaration - ? `class ${scope.name.text}` + ? `class '${scope.name.text}'` : scope.name.text - ? `class expression ${scope.name.text}` + ? `class expression '${scope.name.text}'` : "anonymous class expression"; } else if (isSourceFile(scope)) { - return `file '${scope.fileName}'`; + return scope.externalModuleIndicator ? "module scope" : "global scope"; } else { return "unknown"; diff --git a/tests/baselines/reference/extractMethod/extractMethod1.js b/tests/baselines/reference/extractMethod/extractMethod1.js index 60c7e301616..10bf2648383 100644 --- a/tests/baselines/reference/extractMethod/extractMethod1.js +++ b/tests/baselines/reference/extractMethod/extractMethod1.js @@ -14,7 +14,7 @@ namespace A { } } } -==SCOPE::function a== +==SCOPE::function 'a'== namespace A { let x = 1; function foo() { @@ -34,7 +34,7 @@ namespace A { } } } -==SCOPE::namespace B== +==SCOPE::namespace 'B'== namespace A { let x = 1; function foo() { @@ -55,7 +55,7 @@ namespace A { } } } -==SCOPE::namespace A== +==SCOPE::namespace 'A'== namespace A { let x = 1; function foo() { @@ -76,7 +76,7 @@ namespace A { return a; } } -==SCOPE::file '/a.ts'== +==SCOPE::global scope== namespace A { let x = 1; function foo() { diff --git a/tests/baselines/reference/extractMethod/extractMethod10.js b/tests/baselines/reference/extractMethod/extractMethod10.js index 02923b7ab50..b2397744680 100644 --- a/tests/baselines/reference/extractMethod/extractMethod10.js +++ b/tests/baselines/reference/extractMethod/extractMethod10.js @@ -9,7 +9,7 @@ namespace A { } } } -==SCOPE::class C== +==SCOPE::class 'C'== namespace A { export interface I { x: number }; class C { @@ -24,7 +24,7 @@ namespace A { } } } -==SCOPE::namespace A== +==SCOPE::namespace 'A'== namespace A { export interface I { x: number }; class C { @@ -39,7 +39,7 @@ namespace A { return a1.x + 10; } } -==SCOPE::file '/a.ts'== +==SCOPE::global scope== namespace A { export interface I { x: number }; class C { diff --git a/tests/baselines/reference/extractMethod/extractMethod11.js b/tests/baselines/reference/extractMethod/extractMethod11.js index 77565e7b0a7..21392a07a7a 100644 --- a/tests/baselines/reference/extractMethod/extractMethod11.js +++ b/tests/baselines/reference/extractMethod/extractMethod11.js @@ -11,7 +11,7 @@ namespace A { } } } -==SCOPE::class C== +==SCOPE::class 'C'== namespace A { let y = 1; class C { @@ -30,7 +30,7 @@ namespace A { } } } -==SCOPE::namespace A== +==SCOPE::namespace 'A'== namespace A { let y = 1; class C { @@ -49,7 +49,7 @@ namespace A { return { __return: a1.x + 10, z }; } } -==SCOPE::file '/a.ts'== +==SCOPE::global scope== namespace A { let y = 1; class C { diff --git a/tests/baselines/reference/extractMethod/extractMethod12.js b/tests/baselines/reference/extractMethod/extractMethod12.js index 8ff6e130dad..7cebab5d3c5 100644 --- a/tests/baselines/reference/extractMethod/extractMethod12.js +++ b/tests/baselines/reference/extractMethod/extractMethod12.js @@ -13,7 +13,7 @@ namespace A { } } } -==SCOPE::class C== +==SCOPE::class 'C'== namespace A { let y = 1; class C { diff --git a/tests/baselines/reference/extractMethod/extractMethod2.js b/tests/baselines/reference/extractMethod/extractMethod2.js index 28c27993d71..3c0d2e7c7b0 100644 --- a/tests/baselines/reference/extractMethod/extractMethod2.js +++ b/tests/baselines/reference/extractMethod/extractMethod2.js @@ -12,7 +12,7 @@ namespace A { } } } -==SCOPE::function a== +==SCOPE::function 'a'== namespace A { let x = 1; function foo() { @@ -30,7 +30,7 @@ namespace A { } } } -==SCOPE::namespace B== +==SCOPE::namespace 'B'== namespace A { let x = 1; function foo() { @@ -48,7 +48,7 @@ namespace A { } } } -==SCOPE::namespace A== +==SCOPE::namespace 'A'== namespace A { let x = 1; function foo() { @@ -66,7 +66,7 @@ namespace A { return foo(); } } -==SCOPE::file '/a.ts'== +==SCOPE::global scope== namespace A { let x = 1; function foo() { diff --git a/tests/baselines/reference/extractMethod/extractMethod3.js b/tests/baselines/reference/extractMethod/extractMethod3.js index e5903ead181..cb28e2755e7 100644 --- a/tests/baselines/reference/extractMethod/extractMethod3.js +++ b/tests/baselines/reference/extractMethod/extractMethod3.js @@ -11,7 +11,7 @@ namespace A { } } } -==SCOPE::function a== +==SCOPE::function 'a'== namespace A { function foo() { } @@ -28,7 +28,7 @@ namespace A { } } } -==SCOPE::namespace B== +==SCOPE::namespace 'B'== namespace A { function foo() { } @@ -45,7 +45,7 @@ namespace A { } } } -==SCOPE::namespace A== +==SCOPE::namespace 'A'== namespace A { function foo() { } @@ -62,7 +62,7 @@ namespace A { return foo(); } } -==SCOPE::file '/a.ts'== +==SCOPE::global scope== namespace A { function foo() { } diff --git a/tests/baselines/reference/extractMethod/extractMethod4.js b/tests/baselines/reference/extractMethod/extractMethod4.js index 6b9e2eed099..07029b2d050 100644 --- a/tests/baselines/reference/extractMethod/extractMethod4.js +++ b/tests/baselines/reference/extractMethod/extractMethod4.js @@ -13,7 +13,7 @@ namespace A { } } } -==SCOPE::function a== +==SCOPE::function 'a'== namespace A { function foo() { } @@ -32,7 +32,7 @@ namespace A { } } } -==SCOPE::namespace B== +==SCOPE::namespace 'B'== namespace A { function foo() { } @@ -51,7 +51,7 @@ namespace A { } } } -==SCOPE::namespace A== +==SCOPE::namespace 'A'== namespace A { function foo() { } @@ -70,7 +70,7 @@ namespace A { return foo(); } } -==SCOPE::file '/a.ts'== +==SCOPE::global scope== namespace A { function foo() { } diff --git a/tests/baselines/reference/extractMethod/extractMethod5.js b/tests/baselines/reference/extractMethod/extractMethod5.js index cf63cb2a932..96a76e426b1 100644 --- a/tests/baselines/reference/extractMethod/extractMethod5.js +++ b/tests/baselines/reference/extractMethod/extractMethod5.js @@ -14,7 +14,7 @@ namespace A { } } } -==SCOPE::function a== +==SCOPE::function 'a'== namespace A { let x = 1; export function foo() { @@ -34,7 +34,7 @@ namespace A { } } } -==SCOPE::namespace B== +==SCOPE::namespace 'B'== namespace A { let x = 1; export function foo() { @@ -55,7 +55,7 @@ namespace A { } } } -==SCOPE::namespace A== +==SCOPE::namespace 'A'== namespace A { let x = 1; export function foo() { @@ -76,7 +76,7 @@ namespace A { return a; } } -==SCOPE::file '/a.ts'== +==SCOPE::global scope== namespace A { let x = 1; export function foo() { diff --git a/tests/baselines/reference/extractMethod/extractMethod6.js b/tests/baselines/reference/extractMethod/extractMethod6.js index 99f52e9febb..d1244ac9596 100644 --- a/tests/baselines/reference/extractMethod/extractMethod6.js +++ b/tests/baselines/reference/extractMethod/extractMethod6.js @@ -14,7 +14,7 @@ namespace A { } } } -==SCOPE::function a== +==SCOPE::function 'a'== namespace A { let x = 1; export function foo() { @@ -34,7 +34,7 @@ namespace A { } } } -==SCOPE::namespace B== +==SCOPE::namespace 'B'== namespace A { let x = 1; export function foo() { @@ -56,7 +56,7 @@ namespace A { } } } -==SCOPE::namespace A== +==SCOPE::namespace 'A'== namespace A { let x = 1; export function foo() { @@ -78,7 +78,7 @@ namespace A { return { __return: foo(), a }; } } -==SCOPE::file '/a.ts'== +==SCOPE::global scope== namespace A { let x = 1; export function foo() { diff --git a/tests/baselines/reference/extractMethod/extractMethod7.js b/tests/baselines/reference/extractMethod/extractMethod7.js index 09d5edaa2c0..da400d8b051 100644 --- a/tests/baselines/reference/extractMethod/extractMethod7.js +++ b/tests/baselines/reference/extractMethod/extractMethod7.js @@ -16,7 +16,7 @@ namespace A { } } } -==SCOPE::function a== +==SCOPE::function 'a'== namespace A { let x = 1; export namespace C { @@ -38,7 +38,7 @@ namespace A { } } } -==SCOPE::namespace B== +==SCOPE::namespace 'B'== namespace A { let x = 1; export namespace C { @@ -62,7 +62,7 @@ namespace A { } } } -==SCOPE::namespace A== +==SCOPE::namespace 'A'== namespace A { let x = 1; export namespace C { @@ -86,7 +86,7 @@ namespace A { return { __return: C.foo(), a }; } } -==SCOPE::file '/a.ts'== +==SCOPE::global scope== namespace A { let x = 1; export namespace C { diff --git a/tests/baselines/reference/extractMethod/extractMethod8.js b/tests/baselines/reference/extractMethod/extractMethod8.js index fe9cf2a92f0..d298387b902 100644 --- a/tests/baselines/reference/extractMethod/extractMethod8.js +++ b/tests/baselines/reference/extractMethod/extractMethod8.js @@ -8,7 +8,7 @@ namespace A { } } } -==SCOPE::function a== +==SCOPE::function 'a'== namespace A { let x = 1; namespace B { @@ -22,7 +22,7 @@ namespace A { } } } -==SCOPE::namespace B== +==SCOPE::namespace 'B'== namespace A { let x = 1; namespace B { @@ -36,7 +36,7 @@ namespace A { } } } -==SCOPE::namespace A== +==SCOPE::namespace 'A'== namespace A { let x = 1; namespace B { @@ -50,7 +50,7 @@ namespace A { return 1 + a1 + x; } } -==SCOPE::file '/a.ts'== +==SCOPE::global scope== namespace A { let x = 1; namespace B { diff --git a/tests/baselines/reference/extractMethod/extractMethod9.js b/tests/baselines/reference/extractMethod/extractMethod9.js index fcc5dcd23de..609e3535d57 100644 --- a/tests/baselines/reference/extractMethod/extractMethod9.js +++ b/tests/baselines/reference/extractMethod/extractMethod9.js @@ -8,7 +8,7 @@ namespace A { } } } -==SCOPE::function a== +==SCOPE::function 'a'== namespace A { export interface I { x: number }; namespace B { @@ -22,7 +22,7 @@ namespace A { } } } -==SCOPE::namespace B== +==SCOPE::namespace 'B'== namespace A { export interface I { x: number }; namespace B { @@ -36,7 +36,7 @@ namespace A { } } } -==SCOPE::namespace A== +==SCOPE::namespace 'A'== namespace A { export interface I { x: number }; namespace B { @@ -50,7 +50,7 @@ namespace A { return a1.x + 10; } } -==SCOPE::file '/a.ts'== +==SCOPE::global scope== namespace A { export interface I { x: number }; namespace B { diff --git a/tests/cases/fourslash/extract-method1.ts b/tests/cases/fourslash/extract-method1.ts index 7f5c2ac2d50..ff061295c8c 100644 --- a/tests/cases/fourslash/extract-method1.ts +++ b/tests/cases/fourslash/extract-method1.ts @@ -13,8 +13,11 @@ //// } goTo.select('start', 'end') -verify.refactorAvailable('Extract Method'); -edit.applyRefactor('Extract Method', "scope_0"); +edit.applyRefactor({ + refactorName: "Extract Method", + actionName: "scope_0", + actionDescription: "Extract function into class 'Foo'", +}); verify.currentFileContentIs( `class Foo { someMethod(m: number) { diff --git a/tests/cases/fourslash/extract-method10.ts b/tests/cases/fourslash/extract-method10.ts index 1a02bfa00f5..ffbee7350e2 100644 --- a/tests/cases/fourslash/extract-method10.ts +++ b/tests/cases/fourslash/extract-method10.ts @@ -1,6 +1,11 @@ /// -//// (x => x)(/*1*/x => x/*2*/)(1); +//// export {}; // Make this a module +//// (x => x)(/*1*/x => x/*2*/)(1); goTo.select('1', '2'); -edit.applyRefactor('Extract Method', 'scope_0'); +edit.applyRefactor({ + refactorName: "Extract Method", + actionName: 'scope_0', + actionDescription: "Extract function into module scope", +}); diff --git a/tests/cases/fourslash/extract-method13.ts b/tests/cases/fourslash/extract-method13.ts index b9b7c0096aa..14a146a80c5 100644 --- a/tests/cases/fourslash/extract-method13.ts +++ b/tests/cases/fourslash/extract-method13.ts @@ -10,10 +10,18 @@ //// } goTo.select('a', 'b'); -edit.applyRefactor('Extract Method', 'scope_0'); +edit.applyRefactor({ + refactorName: "Extract Method", + actionName: "scope_0", + actionDescription: "Extract function into class 'C'", +}); goTo.select('c', 'd'); -edit.applyRefactor('Extract Method', 'scope_0'); +edit.applyRefactor({ + refactorName: "Extract Method", + actionName: "scope_0", + actionDescription: "Extract function into class 'C'", +}); verify.currentFileContentIs(`class C { static j = C.newFunction_1(); diff --git a/tests/cases/fourslash/extract-method14.ts b/tests/cases/fourslash/extract-method14.ts index c8bab1b3a56..696bb664bd3 100644 --- a/tests/cases/fourslash/extract-method14.ts +++ b/tests/cases/fourslash/extract-method14.ts @@ -11,7 +11,11 @@ //// } goTo.select('a', 'b'); -edit.applyRefactor('Extract Method', 'scope_1'); +edit.applyRefactor({ + refactorName: "Extract Method", + actionName: "scope_1", + actionDescription: "Extract function into global scope", +}); verify.currentFileContentIs(`function foo() { var i = 10; var __return: any; diff --git a/tests/cases/fourslash/extract-method15.ts b/tests/cases/fourslash/extract-method15.ts index ef62bd3fd3f..93aa357cfee 100644 --- a/tests/cases/fourslash/extract-method15.ts +++ b/tests/cases/fourslash/extract-method15.ts @@ -9,7 +9,11 @@ //// } goTo.select('a', 'b'); -edit.applyRefactor('Extract Method', 'scope_1'); +edit.applyRefactor({ + refactorName: "Extract Method", + actionName: "scope_1", + actionDescription: "Extract function into global scope", +}); verify.currentFileContentIs(`function foo() { var i = 10; diff --git a/tests/cases/fourslash/extract-method18.ts b/tests/cases/fourslash/extract-method18.ts index 9d87979a1ed..d99d14bac73 100644 --- a/tests/cases/fourslash/extract-method18.ts +++ b/tests/cases/fourslash/extract-method18.ts @@ -9,8 +9,11 @@ //// } goTo.select('a', 'b') -verify.refactorAvailable('Extract Method'); -edit.applyRefactor('Extract Method', "scope_1"); +edit.applyRefactor({ + refactorName: "Extract Method", + actionName: "scope_1", + actionDescription: "Extract function into global scope", +}); verify.currentFileContentIs(`function fn() { const x = { m: 1 }; newFunction(x); diff --git a/tests/cases/fourslash/extract-method19.ts b/tests/cases/fourslash/extract-method19.ts index 54f79311cc7..e4fb3e6e111 100644 --- a/tests/cases/fourslash/extract-method19.ts +++ b/tests/cases/fourslash/extract-method19.ts @@ -5,12 +5,15 @@ //// function fn() { //// /*a*/console.log("hi");/*b*/ //// } -//// +//// //// function newFunction() { } goTo.select('a', 'b') -verify.refactorAvailable('Extract Method'); -edit.applyRefactor('Extract Method', "scope_0"); +edit.applyRefactor({ + refactorName: "Extract Method", + actionName: "scope_0", + actionDescription: "Extract function into function 'fn'", +}); verify.currentFileContentIs(`function fn() { newFunction_1(); diff --git a/tests/cases/fourslash/extract-method2.ts b/tests/cases/fourslash/extract-method2.ts index 0a4f346307b..508836c4199 100644 --- a/tests/cases/fourslash/extract-method2.ts +++ b/tests/cases/fourslash/extract-method2.ts @@ -10,8 +10,11 @@ //// } //// } goTo.select('start', 'end') -verify.refactorAvailable('Extract Method'); -edit.applyRefactor('Extract Method', "scope_2"); +edit.applyRefactor({ + refactorName: "Extract Method", + actionName: "scope_2", + actionDescription: "Extract function into global scope", +}); verify.currentFileContentIs( `namespace NS { class Q { diff --git a/tests/cases/fourslash/extract-method21.ts b/tests/cases/fourslash/extract-method21.ts index 0168daf5fcb..c32df5b5979 100644 --- a/tests/cases/fourslash/extract-method21.ts +++ b/tests/cases/fourslash/extract-method21.ts @@ -12,7 +12,11 @@ goTo.select('start', 'end') verify.refactorAvailable('Extract Method'); -edit.applyRefactor('Extract Method', "scope_0"); +edit.applyRefactor({ + refactorName: "Extract Method", + actionName: "scope_0", + actionDescription: "Extract function into class 'Foo'", +}); verify.currentFileContentIs(`class Foo { static method() { diff --git a/tests/cases/fourslash/extract-method24.ts b/tests/cases/fourslash/extract-method24.ts index 9eebc00316b..615cb2ac4d2 100644 --- a/tests/cases/fourslash/extract-method24.ts +++ b/tests/cases/fourslash/extract-method24.ts @@ -7,7 +7,11 @@ //// } goTo.select('a', 'b') -edit.applyRefactor('Extract Method', 'scope_1'); +edit.applyRefactor({ + refactorName: "Extract Method", + actionName: "scope_1", + actionDescription: "Extract function into global scope", +}); verify.currentFileContentIs(`function M() { let a = [1,2,3]; let x = 0; diff --git a/tests/cases/fourslash/extract-method25.ts b/tests/cases/fourslash/extract-method25.ts index ac7e7a23004..8585dd06fd4 100644 --- a/tests/cases/fourslash/extract-method25.ts +++ b/tests/cases/fourslash/extract-method25.ts @@ -8,7 +8,11 @@ //// } goTo.select('a', 'b') -edit.applyRefactor('Extract Method', 'scope_0'); +edit.applyRefactor({ + refactorName: "Extract Method", + actionName: "scope_0", + actionDescription: "Extract function into function 'fn'", +}); verify.currentFileContentIs(`function fn() { var q = newFunction() q[0]++ diff --git a/tests/cases/fourslash/extract-method3.ts b/tests/cases/fourslash/extract-method3.ts index af543121eeb..27a520d0555 100644 --- a/tests/cases/fourslash/extract-method3.ts +++ b/tests/cases/fourslash/extract-method3.ts @@ -10,7 +10,7 @@ //// } //// } -// Don't offer to to 'extract method' a single identifier +// Don't offer to 'extract method' a single identifier goTo.marker('a'); verify.not.refactorAvailable('Extract Method'); diff --git a/tests/cases/fourslash/extract-method5.ts b/tests/cases/fourslash/extract-method5.ts index ac09f92cc05..10294298b08 100644 --- a/tests/cases/fourslash/extract-method5.ts +++ b/tests/cases/fourslash/extract-method5.ts @@ -9,7 +9,11 @@ //// } goTo.select('start', 'end'); -edit.applyRefactor('Extract Method', 'scope_0'); +edit.applyRefactor({ + refactorName: "Extract Method", + actionName: "scope_0", + actionDescription: "Extract function into function 'f'", +}); verify.currentFileContentIs( `function f() { var x: 1 | 2 | 3 = newFunction(); diff --git a/tests/cases/fourslash/extract-method7.ts b/tests/cases/fourslash/extract-method7.ts index 4c95c6a551d..95c9cbe9897 100644 --- a/tests/cases/fourslash/extract-method7.ts +++ b/tests/cases/fourslash/extract-method7.ts @@ -7,7 +7,11 @@ //// } goTo.select('a', 'b'); -edit.applyRefactor('Extract Method', 'scope_0'); +edit.applyRefactor({ + refactorName: "Extract Method", + actionName: "scope_0", + actionDescription: "Extract function into global scope", +}); verify.currentFileContentIs(`function fn(x = newFunction()) { } function newFunction() { diff --git a/tests/cases/fourslash/fourslash.ts b/tests/cases/fourslash/fourslash.ts index abb2c38c6b4..652ed4812c0 100644 --- a/tests/cases/fourslash/fourslash.ts +++ b/tests/cases/fourslash/fourslash.ts @@ -159,7 +159,7 @@ declare namespace FourSlashInterface { codeFixDiagnosticsAvailableAtMarkers(markerNames: string[], diagnosticCode?: number): void; applicableRefactorAvailableForRange(): void; - refactorAvailable(name?: string, subName?: string); + refactorAvailable(name: string, actionName?: string); } class verify extends verifyNegatable { assertHasRanges(ranges: Range[]): void; @@ -310,7 +310,7 @@ declare namespace FourSlashInterface { enableFormatting(): void; disableFormatting(): void; - applyRefactor(refactorName: string, actionName: string): void; + applyRefactor(options: { refactorName: string, actionName: string, actionDescription: string }): void; } class debug { printCurrentParameterHelp(): void; From fe1242c8a902a2f530e5fb91b8f8f3e90bd4f4a6 Mon Sep 17 00:00:00 2001 From: Andy Date: Fri, 25 Aug 2017 09:53:56 -0700 Subject: [PATCH 20/26] Don't try to extract `import` to a method (#18025) --- src/compiler/utilities.ts | 117 ++++++++++-------- src/services/refactors/extractMethod.ts | 27 ++-- .../extract-method-not-for-import.ts | 10 ++ 3 files changed, 87 insertions(+), 67 deletions(-) create mode 100644 tests/cases/fourslash/extract-method-not-for-import.ts diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index 7462bf02adc..9f679011dd0 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -4951,54 +4951,60 @@ namespace ts { || kind === SyntaxKind.NoSubstitutionTemplateLiteral; } - function isLeftHandSideExpressionKind(kind: SyntaxKind): boolean { - return kind === SyntaxKind.PropertyAccessExpression - || kind === SyntaxKind.ElementAccessExpression - || kind === SyntaxKind.NewExpression - || kind === SyntaxKind.CallExpression - || kind === SyntaxKind.JsxElement - || kind === SyntaxKind.JsxSelfClosingElement - || kind === SyntaxKind.TaggedTemplateExpression - || kind === SyntaxKind.ArrayLiteralExpression - || kind === SyntaxKind.ParenthesizedExpression - || kind === SyntaxKind.ObjectLiteralExpression - || kind === SyntaxKind.ClassExpression - || kind === SyntaxKind.FunctionExpression - || kind === SyntaxKind.Identifier - || kind === SyntaxKind.RegularExpressionLiteral - || kind === SyntaxKind.NumericLiteral - || kind === SyntaxKind.StringLiteral - || kind === SyntaxKind.NoSubstitutionTemplateLiteral - || kind === SyntaxKind.TemplateExpression - || kind === SyntaxKind.FalseKeyword - || kind === SyntaxKind.NullKeyword - || kind === SyntaxKind.ThisKeyword - || kind === SyntaxKind.TrueKeyword - || kind === SyntaxKind.SuperKeyword - || kind === SyntaxKind.ImportKeyword - || kind === SyntaxKind.NonNullExpression - || kind === SyntaxKind.MetaProperty; - } - /* @internal */ export function isLeftHandSideExpression(node: Node): node is LeftHandSideExpression { - return isLeftHandSideExpressionKind(skipPartiallyEmittedExpressions(node).kind); - } - - function isUnaryExpressionKind(kind: SyntaxKind): boolean { - return kind === SyntaxKind.PrefixUnaryExpression - || kind === SyntaxKind.PostfixUnaryExpression - || kind === SyntaxKind.DeleteExpression - || kind === SyntaxKind.TypeOfExpression - || kind === SyntaxKind.VoidExpression - || kind === SyntaxKind.AwaitExpression - || kind === SyntaxKind.TypeAssertionExpression - || isLeftHandSideExpressionKind(kind); + switch (node.kind) { + case SyntaxKind.PropertyAccessExpression: + case SyntaxKind.ElementAccessExpression: + case SyntaxKind.NewExpression: + case SyntaxKind.CallExpression: + case SyntaxKind.JsxElement: + case SyntaxKind.JsxSelfClosingElement: + case SyntaxKind.TaggedTemplateExpression: + case SyntaxKind.ArrayLiteralExpression: + case SyntaxKind.ParenthesizedExpression: + case SyntaxKind.ObjectLiteralExpression: + case SyntaxKind.ClassExpression: + case SyntaxKind.FunctionExpression: + case SyntaxKind.Identifier: + case SyntaxKind.RegularExpressionLiteral: + case SyntaxKind.NumericLiteral: + case SyntaxKind.StringLiteral: + case SyntaxKind.NoSubstitutionTemplateLiteral: + case SyntaxKind.TemplateExpression: + case SyntaxKind.FalseKeyword: + case SyntaxKind.NullKeyword: + case SyntaxKind.ThisKeyword: + case SyntaxKind.TrueKeyword: + case SyntaxKind.SuperKeyword: + case SyntaxKind.NonNullExpression: + case SyntaxKind.MetaProperty: + return true; + case SyntaxKind.PartiallyEmittedExpression: + return isLeftHandSideExpression((node as PartiallyEmittedExpression).expression); + case SyntaxKind.ImportKeyword: + return node.parent.kind === SyntaxKind.CallExpression; + default: + return false; + } } /* @internal */ export function isUnaryExpression(node: Node): node is UnaryExpression { - return isUnaryExpressionKind(skipPartiallyEmittedExpressions(node).kind); + switch (node.kind) { + case SyntaxKind.PrefixUnaryExpression: + case SyntaxKind.PostfixUnaryExpression: + case SyntaxKind.DeleteExpression: + case SyntaxKind.TypeOfExpression: + case SyntaxKind.VoidExpression: + case SyntaxKind.AwaitExpression: + case SyntaxKind.TypeAssertionExpression: + return true; + case SyntaxKind.PartiallyEmittedExpression: + return isUnaryExpression((node as PartiallyEmittedExpression).expression); + default: + return isLeftHandSideExpression(node); + } } /* @internal */ @@ -5014,21 +5020,22 @@ namespace ts { } } - function isExpressionKind(kind: SyntaxKind) { - return kind === SyntaxKind.ConditionalExpression - || kind === SyntaxKind.YieldExpression - || kind === SyntaxKind.ArrowFunction - || kind === SyntaxKind.BinaryExpression - || kind === SyntaxKind.SpreadElement - || kind === SyntaxKind.AsExpression - || kind === SyntaxKind.OmittedExpression - || kind === SyntaxKind.CommaListExpression - || isUnaryExpressionKind(kind); - } - /* @internal */ export function isExpression(node: Node): node is Expression { - return isExpressionKind(skipPartiallyEmittedExpressions(node).kind); + switch (node.kind) { + case SyntaxKind.ConditionalExpression: + case SyntaxKind.YieldExpression: + case SyntaxKind.ArrowFunction: + case SyntaxKind.BinaryExpression: + case SyntaxKind.SpreadElement: + case SyntaxKind.AsExpression: + case SyntaxKind.OmittedExpression: + case SyntaxKind.CommaListExpression: + case SyntaxKind.PartiallyEmittedExpression: + return true; + default: + return isUnaryExpression(node); + } } export function isAssertionExpression(node: Node): node is AssertionExpression { diff --git a/src/services/refactors/extractMethod.ts b/src/services/refactors/extractMethod.ts index f32321aff2a..820212011a9 100644 --- a/src/services/refactors/extractMethod.ts +++ b/src/services/refactors/extractMethod.ts @@ -231,18 +231,7 @@ namespace ts.refactor.extractMethod { if (errors) { return { errors }; } - - // If our selection is the expression in an ExpressionStatement, expand - // the selection to include the enclosing Statement (this stops us - // from trying to care about the return value of the extracted function - // and eliminates double semicolon insertion in certain scenarios) - const range = isStatement(start) - ? [start] - : start.parent && start.parent.kind === SyntaxKind.ExpressionStatement - ? [start.parent as Statement] - : start as Expression; - - return { targetRange: { range, facts: rangeFacts, declarations } }; + return { targetRange: { range: getStatementOrExpressionRange(start), facts: rangeFacts, declarations } }; } function createErrorResult(sourceFile: SourceFile, start: number, length: number, message: DiagnosticMessage): RangeToExtract { @@ -459,6 +448,20 @@ namespace ts.refactor.extractMethod { } } + function getStatementOrExpressionRange(node: Node): Statement[] | Expression { + if (isStatement(node)) { + return [node]; + } + else if (isExpression(node)) { + // If our selection is the expression in an ExpressionStatement, expand + // the selection to include the enclosing Statement (this stops us + // from trying to care about the return value of the extracted function + // and eliminates double semicolon insertion in certain scenarios) + return isExpressionStatement(node.parent) ? [node.parent] : node; + } + return undefined; + } + function isValidExtractionTarget(node: Node): node is Scope { // Note that we don't use isFunctionLike because we don't want to put the extracted closure *inside* a method return (node.kind === SyntaxKind.FunctionDeclaration) || isSourceFile(node) || isModuleBlock(node) || isClassLike(node); diff --git a/tests/cases/fourslash/extract-method-not-for-import.ts b/tests/cases/fourslash/extract-method-not-for-import.ts new file mode 100644 index 00000000000..a72d793611d --- /dev/null +++ b/tests/cases/fourslash/extract-method-not-for-import.ts @@ -0,0 +1,10 @@ +/// + +// @Filename: /a.ts +////i/**/mport _ from "./b"; + +// @Filename: /b.ts +////export default function f() {} + +goTo.marker(""); +verify.not.refactorAvailable('Extract Method'); From bdfb92aebe304f8b75d9cd8dcda5f23898278244 Mon Sep 17 00:00:00 2001 From: Ron Buckton Date: Fri, 25 Aug 2017 14:17:48 -0700 Subject: [PATCH 21/26] Fix crash in name resolution with custom transforms and emitDecoratorMetadata --- src/compiler/checker.ts | 9 +++++++ src/compiler/transformers/ts.ts | 2 +- src/harness/unittests/customTransforms.ts | 23 ++++++++++++++-- .../customTransforms/before+decorators.js | 26 +++++++++++++++++++ 4 files changed, 57 insertions(+), 3 deletions(-) create mode 100644 tests/baselines/reference/customTransforms/before+decorators.js diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 4bdfa4b7250..79509804105 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -23508,6 +23508,15 @@ namespace ts { } function getTypeReferenceSerializationKind(typeName: EntityName, location?: Node): TypeReferenceSerializationKind { + // ensure both `typeName` and `location` are parse tree nodes. + typeName = getParseTreeNode(typeName, isEntityName); + if (!typeName) return TypeReferenceSerializationKind.Unknown; + + if (location) { + location = getParseTreeNode(location); + if (!location) return TypeReferenceSerializationKind.Unknown; + } + // Resolve the symbol as a value to ensure the type can be reached at runtime during emit. const valueSymbol = resolveEntityName(typeName, SymbolFlags.Value, /*ignoreErrors*/ true, /*dontResolveAlias*/ false, location); diff --git a/src/compiler/transformers/ts.ts b/src/compiler/transformers/ts.ts index 4c679ea1eee..a7942397391 100644 --- a/src/compiler/transformers/ts.ts +++ b/src/compiler/transformers/ts.ts @@ -1944,7 +1944,7 @@ namespace ts { const name = getMutableClone(node); name.flags &= ~NodeFlags.Synthesized; name.original = undefined; - name.parent = currentScope; + name.parent = getParseTreeNode(currentScope); // ensure the parent is set to a parse tree node. if (useFallback) { return createLogicalAnd( createStrictInequality( diff --git a/src/harness/unittests/customTransforms.ts b/src/harness/unittests/customTransforms.ts index 4b05ebb9ea7..f47bdad5d58 100644 --- a/src/harness/unittests/customTransforms.ts +++ b/src/harness/unittests/customTransforms.ts @@ -3,12 +3,11 @@ namespace ts { describe("customTransforms", () => { - function emitsCorrectly(name: string, sources: { file: string, text: string }[], customTransformers: CustomTransformers) { + function emitsCorrectly(name: string, sources: { file: string, text: string }[], customTransformers: CustomTransformers, options: CompilerOptions = {}) { it(name, () => { const roots = sources.map(source => createSourceFile(source.file, source.text, ScriptTarget.ES2015)); const fileMap = arrayToMap(roots, file => file.fileName); const outputs = createMap(); - const options: CompilerOptions = {}; const host: CompilerHost = { getSourceFile: (fileName) => fileMap.get(fileName), getDefaultLibFileName: () => "lib.d.ts", @@ -82,5 +81,25 @@ namespace ts { emitsCorrectly("before", sources, { before: [before] }); emitsCorrectly("after", sources, { after: [after] }); emitsCorrectly("both", sources, { before: [before], after: [after] }); + + emitsCorrectly("before+decorators", [{ + file: "source.ts", + text: ` + declare const dec: any; + class B {} + @dec export class C { constructor(b: B) { } } + 'change' + ` + }], {before: [ + context => node => visitNode(node, function visitor(node: Node): Node { + if (isStringLiteral(node) && node.text === "change") return createLiteral("changed"); + return visitEachChild(node, visitor, context); + }) + ]}, { + target: ScriptTarget.ES5, + module: ModuleKind.ES2015, + emitDecoratorMetadata: true, + experimentalDecorators: true + }); }); } \ No newline at end of file diff --git a/tests/baselines/reference/customTransforms/before+decorators.js b/tests/baselines/reference/customTransforms/before+decorators.js new file mode 100644 index 00000000000..ef705f0c9d8 --- /dev/null +++ b/tests/baselines/reference/customTransforms/before+decorators.js @@ -0,0 +1,26 @@ +// [source.js] +var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { + var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; + if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); + else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; + return c > 3 && r && Object.defineProperty(target, key, r), r; +}; +var __metadata = (this && this.__metadata) || function (k, v) { + if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v); +}; +var B = /** @class */ (function () { + function B() { + } + return B; +}()); +var C = /** @class */ (function () { + function C(b) { + } + C = __decorate([ + dec, + __metadata("design:paramtypes", [B]) + ], C); + return C; +}()); +export { C }; +"changed"; From 38c3f67652587d50b9de59c0b13dc92c0c16c35f Mon Sep 17 00:00:00 2001 From: Wesley Wigham Date: Fri, 25 Aug 2017 15:10:47 -0700 Subject: [PATCH 22/26] Visit destructuring computed names (#18052) --- src/compiler/transformers/destructuring.ts | 2 +- .../computerPropertiesInES5ShouldBeTransformed.js | 8 ++++++++ .../computerPropertiesInES5ShouldBeTransformed.symbols | 6 ++++++ .../computerPropertiesInES5ShouldBeTransformed.types | 8 ++++++++ .../computerPropertiesInES5ShouldBeTransformed.ts | 2 ++ 5 files changed, 25 insertions(+), 1 deletion(-) create mode 100644 tests/baselines/reference/computerPropertiesInES5ShouldBeTransformed.js create mode 100644 tests/baselines/reference/computerPropertiesInES5ShouldBeTransformed.symbols create mode 100644 tests/baselines/reference/computerPropertiesInES5ShouldBeTransformed.types create mode 100644 tests/cases/compiler/computerPropertiesInES5ShouldBeTransformed.ts diff --git a/src/compiler/transformers/destructuring.ts b/src/compiler/transformers/destructuring.ts index 2249e577312..0fea3e41c87 100644 --- a/src/compiler/transformers/destructuring.ts +++ b/src/compiler/transformers/destructuring.ts @@ -409,7 +409,7 @@ namespace ts { */ function createDestructuringPropertyAccess(flattenContext: FlattenContext, value: Expression, propertyName: PropertyName): LeftHandSideExpression { if (isComputedPropertyName(propertyName)) { - const argumentExpression = ensureIdentifier(flattenContext, propertyName.expression, /*reuseIdentifierExpressions*/ false, /*location*/ propertyName); + const argumentExpression = ensureIdentifier(flattenContext, visitNode(propertyName.expression, flattenContext.visitor), /*reuseIdentifierExpressions*/ false, /*location*/ propertyName); return createElementAccess(value, argumentExpression); } else if (isStringOrNumericLiteral(propertyName)) { diff --git a/tests/baselines/reference/computerPropertiesInES5ShouldBeTransformed.js b/tests/baselines/reference/computerPropertiesInES5ShouldBeTransformed.js new file mode 100644 index 00000000000..973e9756425 --- /dev/null +++ b/tests/baselines/reference/computerPropertiesInES5ShouldBeTransformed.js @@ -0,0 +1,8 @@ +//// [computerPropertiesInES5ShouldBeTransformed.ts] +const b = ({ [`key`]: renamed }) => renamed; + +//// [computerPropertiesInES5ShouldBeTransformed.js] +var b = function (_a) { + var _b = "key", renamed = _a[_b]; + return renamed; +}; diff --git a/tests/baselines/reference/computerPropertiesInES5ShouldBeTransformed.symbols b/tests/baselines/reference/computerPropertiesInES5ShouldBeTransformed.symbols new file mode 100644 index 00000000000..c118c1d0eff --- /dev/null +++ b/tests/baselines/reference/computerPropertiesInES5ShouldBeTransformed.symbols @@ -0,0 +1,6 @@ +=== tests/cases/compiler/computerPropertiesInES5ShouldBeTransformed.ts === +const b = ({ [`key`]: renamed }) => renamed; +>b : Symbol(b, Decl(computerPropertiesInES5ShouldBeTransformed.ts, 0, 5)) +>renamed : Symbol(renamed, Decl(computerPropertiesInES5ShouldBeTransformed.ts, 0, 12)) +>renamed : Symbol(renamed, Decl(computerPropertiesInES5ShouldBeTransformed.ts, 0, 12)) + diff --git a/tests/baselines/reference/computerPropertiesInES5ShouldBeTransformed.types b/tests/baselines/reference/computerPropertiesInES5ShouldBeTransformed.types new file mode 100644 index 00000000000..b0e4e1424ac --- /dev/null +++ b/tests/baselines/reference/computerPropertiesInES5ShouldBeTransformed.types @@ -0,0 +1,8 @@ +=== tests/cases/compiler/computerPropertiesInES5ShouldBeTransformed.ts === +const b = ({ [`key`]: renamed }) => renamed; +>b : ({ [`key`]: renamed }: {}) => any +>({ [`key`]: renamed }) => renamed : ({ [`key`]: renamed }: {}) => any +>`key` : "key" +>renamed : any +>renamed : any + diff --git a/tests/cases/compiler/computerPropertiesInES5ShouldBeTransformed.ts b/tests/cases/compiler/computerPropertiesInES5ShouldBeTransformed.ts new file mode 100644 index 00000000000..42eb602989d --- /dev/null +++ b/tests/cases/compiler/computerPropertiesInES5ShouldBeTransformed.ts @@ -0,0 +1,2 @@ +// @target: es5 +const b = ({ [`key`]: renamed }) => renamed; \ No newline at end of file From 4d05bfdf4aa3aab07e696f34f0912ba93e01cc51 Mon Sep 17 00:00:00 2001 From: Andy Date: Fri, 25 Aug 2017 15:14:51 -0700 Subject: [PATCH 23/26] `moduleAugmentations` may contain an `Identifier` (#18009) * `moduleAugmentations` may contain an `Identifier` * Add comment * Rename function --- src/compiler/checker.ts | 7 ++++--- src/compiler/program.ts | 34 +++++++++++++++++++++------------- src/compiler/types.ts | 3 ++- 3 files changed, 27 insertions(+), 17 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index c7513deba56..088cb4bb851 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -640,7 +640,7 @@ namespace ts { }); } - function mergeModuleAugmentation(moduleName: LiteralExpression): void { + function mergeModuleAugmentation(moduleName: StringLiteral | Identifier): void { const moduleAugmentation = moduleName.parent; if (moduleAugmentation.symbol.declarations[0] !== moduleAugmentation) { // this is a combined symbol for multiple augmentations within the same file. @@ -672,7 +672,8 @@ namespace ts { mergeSymbol(mainModule, moduleAugmentation.symbol); } else { - error(moduleName, Diagnostics.Cannot_augment_module_0_because_it_resolves_to_a_non_module_entity, moduleName.text); + // moduleName will be a StringLiteral since this is not `declare global`. + error(moduleName, Diagnostics.Cannot_augment_module_0_because_it_resolves_to_a_non_module_entity, (moduleName as StringLiteral).text); } } } @@ -23800,7 +23801,7 @@ namespace ts { } // Initialize global symbol table - let augmentations: ReadonlyArray[]; + let augmentations: ReadonlyArray[]; for (const file of host.getSourceFiles()) { if (!isExternalOrCommonJsModule(file)) { mergeSymbolTable(globals, file.locals); diff --git a/src/compiler/program.ts b/src/compiler/program.ts index 800e2f9e619..ec220ad248e 100644 --- a/src/compiler/program.ts +++ b/src/compiler/program.ts @@ -890,7 +890,7 @@ namespace ts { for (const { oldFile: oldSourceFile, newFile: newSourceFile } of modifiedSourceFiles) { const newSourceFilePath = getNormalizedAbsolutePath(newSourceFile.fileName, currentDirectory); if (resolveModuleNamesWorker) { - const moduleNames = map(concatenate(newSourceFile.imports, newSourceFile.moduleAugmentations), getTextOfLiteral); + const moduleNames = getModuleNames(newSourceFile); const oldProgramState = { program: oldProgram, file: oldSourceFile, modifiedFilePaths }; const resolutions = resolveModuleNamesReusingOldState(moduleNames, newSourceFilePath, newSourceFile, oldProgramState); // ensure that module resolution results are still correct @@ -1434,12 +1434,10 @@ namespace ts { return a.fileName === b.fileName; } - function moduleNameIsEqualTo(a: LiteralExpression, b: LiteralExpression): boolean { - return a.text === b.text; - } - - function getTextOfLiteral(literal: LiteralExpression): string { - return literal.text; + function moduleNameIsEqualTo(a: StringLiteral | Identifier, b: StringLiteral | Identifier): boolean { + return a.kind === SyntaxKind.StringLiteral + ? b.kind === SyntaxKind.StringLiteral && a.text === b.text + : b.kind === SyntaxKind.Identifier && a.escapedText === b.escapedText; } function collectExternalModuleReferences(file: SourceFile): void { @@ -1452,7 +1450,7 @@ namespace ts { // file.imports may not be undefined if there exists dynamic import let imports: StringLiteral[]; - let moduleAugmentations: StringLiteral[]; + let moduleAugmentations: Array; let ambientModules: string[]; // If we are importing helpers, we need to add a synthetic reference to resolve the @@ -1481,7 +1479,7 @@ namespace ts { return; - function collectModuleReferences(node: Node, inAmbientModule: boolean): void { + function collectModuleReferences(node: Statement, inAmbientModule: boolean): void { switch (node.kind) { case SyntaxKind.ImportDeclaration: case SyntaxKind.ImportEqualsDeclaration: @@ -1503,8 +1501,8 @@ namespace ts { break; case SyntaxKind.ModuleDeclaration: if (isAmbientModule(node) && (inAmbientModule || hasModifier(node, ModifierFlags.Ambient) || file.isDeclarationFile)) { - const moduleName = (node).name; // TODO: GH#17347 - const nameText = ts.getTextOfIdentifierOrLiteral(moduleName); + const moduleName = (node).name; + const nameText = getTextOfIdentifierOrLiteral(moduleName); // Ambient module declarations can be interpreted as augmentations for some existing external modules. // This will happen in two cases: // - if current file is external module then module augmentation is a ambient module declaration defined in the top level scope @@ -1821,8 +1819,7 @@ namespace ts { collectExternalModuleReferences(file); if (file.imports.length || file.moduleAugmentations.length) { // Because global augmentation doesn't have string literal name, we can check for global augmentation as such. - const nonGlobalAugmentation = filter(file.moduleAugmentations, (moduleAugmentation) => moduleAugmentation.kind === SyntaxKind.StringLiteral); - const moduleNames = map(concatenate(file.imports, nonGlobalAugmentation), getTextOfLiteral); + const moduleNames = getModuleNames(file); const oldProgramState = { program: oldProgram, file, modifiedFilePaths }; const resolutions = resolveModuleNamesReusingOldState(moduleNames, getNormalizedAbsolutePath(file.fileName, currentDirectory), file, oldProgramState); Debug.assert(resolutions.length === moduleNames.length); @@ -2233,4 +2230,15 @@ namespace ts { Debug.assert(names.every(name => name !== undefined), "A name is undefined.", () => JSON.stringify(names)); return names; } + + function getModuleNames({ imports, moduleAugmentations }: SourceFile): string[] { + const res = imports.map(i => i.text); + for (const aug of moduleAugmentations) { + if (aug.kind === SyntaxKind.StringLiteral) { + res.push(aug.text); + } + // Do nothing if it's an Identifier; we don't need to do module resolution for `declare global`. + } + return res; + } } diff --git a/src/compiler/types.ts b/src/compiler/types.ts index f5cca6a6820..87ac4afbab1 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -2343,7 +2343,8 @@ namespace ts { /* @internal */ resolvedModules: Map; /* @internal */ resolvedTypeReferenceDirectiveNames: Map; /* @internal */ imports: ReadonlyArray; - /* @internal */ moduleAugmentations: ReadonlyArray; + // Identifier only if `declare global` + /* @internal */ moduleAugmentations: ReadonlyArray; /* @internal */ patternAmbientModules?: PatternAmbientModule[]; /* @internal */ ambientModuleNames: ReadonlyArray; /* @internal */ checkJsDirective: CheckJsDirective | undefined; From e73b10a304430d7022aca26623e1a286d69f7afe Mon Sep 17 00:00:00 2001 From: Andy Date: Fri, 25 Aug 2017 15:15:16 -0700 Subject: [PATCH 24/26] Use isPartOfExpression in extractMethod, not isExpression (#18047) * Use isPartOfExpression in extractMethod, not isExpression * Add whitespace --- src/compiler/utilities.ts | 33 ++++++++++++++++--------- src/services/refactors/extractMethod.ts | 6 ++--- 2 files changed, 25 insertions(+), 14 deletions(-) diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index 9f679011dd0..a32a82ecf10 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -4953,7 +4953,11 @@ namespace ts { /* @internal */ export function isLeftHandSideExpression(node: Node): node is LeftHandSideExpression { - switch (node.kind) { + return isLeftHandSideExpressionKind(skipPartiallyEmittedExpressions(node).kind); + } + + function isLeftHandSideExpressionKind(kind: SyntaxKind): boolean { + switch (kind) { case SyntaxKind.PropertyAccessExpression: case SyntaxKind.ElementAccessExpression: case SyntaxKind.NewExpression: @@ -4979,11 +4983,8 @@ namespace ts { case SyntaxKind.SuperKeyword: case SyntaxKind.NonNullExpression: case SyntaxKind.MetaProperty: + case SyntaxKind.ImportKeyword: // technically this is only an Expression if it's in a CallExpression return true; - case SyntaxKind.PartiallyEmittedExpression: - return isLeftHandSideExpression((node as PartiallyEmittedExpression).expression); - case SyntaxKind.ImportKeyword: - return node.parent.kind === SyntaxKind.CallExpression; default: return false; } @@ -4991,7 +4992,11 @@ namespace ts { /* @internal */ export function isUnaryExpression(node: Node): node is UnaryExpression { - switch (node.kind) { + return isUnaryExpressionKind(skipPartiallyEmittedExpressions(node).kind); + } + + function isUnaryExpressionKind(kind: SyntaxKind): boolean { + switch (kind) { case SyntaxKind.PrefixUnaryExpression: case SyntaxKind.PostfixUnaryExpression: case SyntaxKind.DeleteExpression: @@ -5000,10 +5005,8 @@ namespace ts { case SyntaxKind.AwaitExpression: case SyntaxKind.TypeAssertionExpression: return true; - case SyntaxKind.PartiallyEmittedExpression: - return isUnaryExpression((node as PartiallyEmittedExpression).expression); default: - return isLeftHandSideExpression(node); + return isLeftHandSideExpressionKind(kind); } } @@ -5021,8 +5024,16 @@ namespace ts { } /* @internal */ + /** + * Determines whether a node is an expression based only on its kind. + * Use `isPartOfExpression` if not in transforms. + */ export function isExpression(node: Node): node is Expression { - switch (node.kind) { + return isExpressionKind(skipPartiallyEmittedExpressions(node).kind); + } + + function isExpressionKind(kind: SyntaxKind): boolean { + switch (kind) { case SyntaxKind.ConditionalExpression: case SyntaxKind.YieldExpression: case SyntaxKind.ArrowFunction: @@ -5034,7 +5045,7 @@ namespace ts { case SyntaxKind.PartiallyEmittedExpression: return true; default: - return isUnaryExpression(node); + return isUnaryExpressionKind(kind); } } diff --git a/src/services/refactors/extractMethod.ts b/src/services/refactors/extractMethod.ts index 820212011a9..4fa22d24b8d 100644 --- a/src/services/refactors/extractMethod.ts +++ b/src/services/refactors/extractMethod.ts @@ -278,7 +278,7 @@ namespace ts.refactor.extractMethod { Continue = 1 << 1, Return = 1 << 2 } - if (!isStatement(nodeToCheck) && !(isExpression(nodeToCheck) && isExtractableExpression(nodeToCheck))) { + if (!isStatement(nodeToCheck) && !(isPartOfExpression(nodeToCheck) && isExtractableExpression(nodeToCheck))) { return [createDiagnosticForNode(nodeToCheck, Messages.StatementOrExpressionExpected)]; } @@ -452,12 +452,12 @@ namespace ts.refactor.extractMethod { if (isStatement(node)) { return [node]; } - else if (isExpression(node)) { + else if (isPartOfExpression(node)) { // If our selection is the expression in an ExpressionStatement, expand // the selection to include the enclosing Statement (this stops us // from trying to care about the return value of the extracted function // and eliminates double semicolon insertion in certain scenarios) - return isExpressionStatement(node.parent) ? [node.parent] : node; + return isExpressionStatement(node.parent) ? [node.parent] : node as Expression; } return undefined; } From a32d99dfc8da65565ea037e76770f0dd2711a735 Mon Sep 17 00:00:00 2001 From: Wesley Wigham Date: Fri, 25 Aug 2017 18:22:03 -0700 Subject: [PATCH 25/26] Use visitNode (#18059) --- src/compiler/transformers/module/module.ts | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/compiler/transformers/module/module.ts b/src/compiler/transformers/module/module.ts index 23bae8714c8..493f5a43a1f 100644 --- a/src/compiler/transformers/module/module.ts +++ b/src/compiler/transformers/module/module.ts @@ -430,11 +430,8 @@ namespace ts { */ function addExportEqualsIfNeeded(statements: Statement[], emitAsReturn: boolean) { if (currentModuleInfo.exportEquals) { - const expressionResult = importCallExpressionVisitor(currentModuleInfo.exportEquals.expression); + const expressionResult = visitNode(currentModuleInfo.exportEquals.expression, importCallExpressionVisitor); if (expressionResult) { - if (expressionResult instanceof Array) { - return Debug.fail("export= expression should never be replaced with multiple expressions!"); - } if (emitAsReturn) { const statement = createReturn(expressionResult); setTextRange(statement, currentModuleInfo.exportEquals); From b21d3f03bd0f8db3c1152bf2ca8fc8dce95cbf0f Mon Sep 17 00:00:00 2001 From: Andy Hanson Date: Mon, 28 Aug 2017 07:48:43 -0700 Subject: [PATCH 26/26] Move browser-resolve to devDependencies --- package.json | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/package.json b/package.json index 8d28b6051c6..9e4b3234770 100644 --- a/package.json +++ b/package.json @@ -49,6 +49,7 @@ "@types/run-sequence": "latest", "@types/through2": "latest", "browserify": "latest", + "browser-resolve": "^1.11.2", "chai": "latest", "convert-source-map": "latest", "del": "latest", @@ -93,8 +94,5 @@ "fs": false, "os": false, "path": false - }, - "dependencies": { - "browser-resolve": "^1.11.2" } }