diff --git a/Jakefile.js b/Jakefile.js index f8be7c2b671..4512aa23794 100644 --- a/Jakefile.js +++ b/Jakefile.js @@ -328,8 +328,14 @@ function compileFile(outFile, sources, prereqs, prefixes, useBuiltCompiler, opts if (opts.stripInternal) { options += " --stripInternal"; } - - options += " --target es5 --lib es5,scripthost --noUnusedLocals --noUnusedParameters"; + options += " --target es5"; + if (opts.lib) { + options += " --lib " + opts.lib + } + else { + options += " --lib es5,scripthost" + } + options += " --noUnusedLocals --noUnusedParameters"; var cmd = host + " " + compilerPath + " " + options + " "; cmd = cmd + sources.join(" "); @@ -1110,7 +1116,7 @@ desc("Compiles tslint rules to js"); task("build-rules", ["build-rules-start"].concat(tslintRulesOutFiles).concat(["build-rules-end"])); tslintRulesFiles.forEach(function (ruleFile, i) { compileFile(tslintRulesOutFiles[i], [ruleFile], [ruleFile], [], /*useBuiltCompiler*/ false, - { noOutFile: true, generateDeclarations: false, outDir: path.join(builtLocalDirectory, "tslint") }); + { noOutFile: true, generateDeclarations: false, outDir: path.join(builtLocalDirectory, "tslint"), lib: "es6" }); }); desc("Emit the start of the build-rules fold"); diff --git a/scripts/parallel-lint.js b/scripts/parallel-lint.js index aec9960ce47..2ac84667d6f 100644 --- a/scripts/parallel-lint.js +++ b/scripts/parallel-lint.js @@ -1,5 +1,6 @@ var tslint = require("tslint"); var fs = require("fs"); +var path = require("path"); function getLinterOptions() { return { @@ -9,7 +10,7 @@ function getLinterOptions() { }; } function getLinterConfiguration() { - return require("../tslint.json"); + return tslint.Configuration.loadConfigurationFromPath(path.join(__dirname, "../tslint.json")); } function lintFileContents(options, configuration, path, contents) { diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 9cd5b7a31d2..a4c0acc405d 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -141,7 +141,7 @@ namespace ts { getAugmentedPropertiesOfType, getRootSymbols, getContextualType: node => { - node = getParseTreeNode(node, isExpression) + node = getParseTreeNode(node, isExpression); return node ? getContextualType(node) : undefined; }, getFullyQualifiedName, @@ -16131,7 +16131,7 @@ namespace ts { } function checkDeclarationInitializer(declaration: VariableLikeDeclaration) { - const type = checkExpressionCached(declaration.initializer); + const type = getTypeOfExpression(declaration.initializer, /*cache*/ true); return getCombinedNodeFlags(declaration) & NodeFlags.Const || getCombinedModifierFlags(declaration) & ModifierFlags.Readonly && !isParameterPropertyDeclaration(declaration) || isTypeAssertion(declaration.initializer) ? type : getWidenedLiteralType(type); @@ -16204,10 +16204,12 @@ namespace ts { // Returns the type of an expression. Unlike checkExpression, this function is simply concerned // with computing the type and may not fully check all contained sub-expressions for errors. - function getTypeOfExpression(node: Expression) { + // A cache argument of true indicates that if the function performs a full type check, it is ok + // to cache the result. + function getTypeOfExpression(node: Expression, cache?: boolean) { // Optimize for the common case of a call to a function with a single non-generic call // signature where we can just fetch the return type without checking the arguments. - if (node.kind === SyntaxKind.CallExpression && (node).expression.kind !== SyntaxKind.SuperKeyword) { + if (node.kind === SyntaxKind.CallExpression && (node).expression.kind !== SyntaxKind.SuperKeyword && !isRequireCall(node, /*checkArgumentIsStringLiteral*/true)) { const funcType = checkNonNullExpression((node).expression); const signature = getSingleCallSignature(funcType); if (signature && !signature.typeParameters) { @@ -16217,7 +16219,7 @@ namespace ts { // Otherwise simply call checkExpression. Ideally, the entire family of checkXXX functions // should have a parameter that indicates whether full error checking is required such that // we can perform the optimizations locally. - return checkExpression(node); + return cache ? checkExpressionCached(node) : checkExpression(node); } // Checks an expression and returns its type. The contextualMapper parameter serves two purposes: When @@ -20674,7 +20676,7 @@ namespace ts { } if (potentialNewTargetCollisions.length) { - forEach(potentialNewTargetCollisions, checkIfNewTargetIsCapturedInEnclosingScope) + forEach(potentialNewTargetCollisions, checkIfNewTargetIsCapturedInEnclosingScope); potentialNewTargetCollisions.length = 0; } diff --git a/src/compiler/core.ts b/src/compiler/core.ts index 71c0f55c9af..c3c0ba77fe4 100644 --- a/src/compiler/core.ts +++ b/src/compiler/core.ts @@ -84,7 +84,7 @@ namespace ts { this.index++; return { value: this.selector(this.data, this.keys[index]), done: false }; } - return { value: undefined as never, done: true } + return { value: undefined as never, done: true }; } } @@ -140,7 +140,7 @@ namespace ts { action(this.data[key], key); } } - } + }; } export function createFileMap(keyMapper?: (key: string) => string): FileMap { diff --git a/src/compiler/declarationEmitter.ts b/src/compiler/declarationEmitter.ts index 12411cabd56..4a542ab30c7 100644 --- a/src/compiler/declarationEmitter.ts +++ b/src/compiler/declarationEmitter.ts @@ -1164,7 +1164,7 @@ namespace ts { emitTypeParameters(node.typeParameters); const baseTypeNode = getClassExtendsHeritageClauseElement(node); if (baseTypeNode) { - node.name + node.name; emitHeritageClause(node.name, [baseTypeNode], /*isImplementsList*/ false); } emitHeritageClause(node.name, getClassImplementsHeritageClauseElements(node), /*isImplementsList*/ true); diff --git a/src/compiler/moduleNameResolver.ts b/src/compiler/moduleNameResolver.ts index f9fbe5b01c1..1911605a0a6 100644 --- a/src/compiler/moduleNameResolver.ts +++ b/src/compiler/moduleNameResolver.ts @@ -675,7 +675,7 @@ namespace ts { } export function nodeModuleNameResolver(moduleName: string, containingFile: string, compilerOptions: CompilerOptions, host: ModuleResolutionHost, cache?: ModuleResolutionCache): ResolvedModuleWithFailedLookupLocations { - return nodeModuleNameResolverWorker(moduleName, containingFile, compilerOptions, host, cache, /* jsOnly*/ false); + return nodeModuleNameResolverWorker(moduleName, containingFile, compilerOptions, host, cache, /*jsOnly*/ false); } /* @internal */ @@ -962,7 +962,7 @@ namespace ts { const result = cache && cache.get(containingDirectory); if (result) { if (traceEnabled) { - trace(host, Diagnostics.Resolution_for_module_0_was_found_in_cache, moduleName) + trace(host, Diagnostics.Resolution_for_module_0_was_found_in_cache, moduleName); } return { value: result.resolvedModule && { path: result.resolvedModule.resolvedFileName, extension: result.resolvedModule.extension } }; } diff --git a/src/compiler/transformer.ts b/src/compiler/transformer.ts index b9a75015f41..d51fdb12ac3 100644 --- a/src/compiler/transformer.ts +++ b/src/compiler/transformer.ts @@ -121,13 +121,13 @@ namespace ts { enableEmitNotification, isSubstitutionEnabled, isEmitNotificationEnabled, - get onSubstituteNode() { return onSubstituteNode }, + get onSubstituteNode() { return onSubstituteNode; }, set onSubstituteNode(value) { Debug.assert(state < TransformationState.Initialized, "Cannot modify transformation hooks after initialization has completed."); Debug.assert(value !== undefined, "Value must not be 'undefined'"); onSubstituteNode = value; }, - get onEmitNode() { return onEmitNode }, + get onEmitNode() { return onEmitNode; }, set onEmitNode(value) { Debug.assert(state < TransformationState.Initialized, "Cannot modify transformation hooks after initialization has completed."); Debug.assert(value !== undefined, "Value must not be 'undefined'"); diff --git a/src/compiler/transformers/es2015.ts b/src/compiler/transformers/es2015.ts index 03d4b9ce328..fd2c287d0e0 100644 --- a/src/compiler/transformers/es2015.ts +++ b/src/compiler/transformers/es2015.ts @@ -2690,7 +2690,7 @@ namespace ts { if (loopOutParameters.length) { copyOutParameters(loopOutParameters, CopyDirection.ToOutParameter, statements); } - addRange(statements, lexicalEnvironment) + addRange(statements, lexicalEnvironment); loopBody = createBlock(statements, /*multiline*/ true); } diff --git a/src/compiler/transformers/module/module.ts b/src/compiler/transformers/module/module.ts index 72a3558b1a3..52c6db707c1 100644 --- a/src/compiler/transformers/module/module.ts +++ b/src/compiler/transformers/module/module.ts @@ -1152,7 +1152,7 @@ namespace ts { createIdentifier("__esModule"), createLiteral(true) ) - ) + ); } else { statement = createStatement( diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 4ffd44e6aa1..53d84118609 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -3297,7 +3297,7 @@ } export interface PluginImport { - name: string + name: string; } export type CompilerOptionsValue = string | number | boolean | (string | number)[] | string[] | MapLike | PluginImport[]; diff --git a/src/harness/fourslash.ts b/src/harness/fourslash.ts index 49d1336c874..2532a1875d9 100644 --- a/src/harness/fourslash.ts +++ b/src/harness/fourslash.ts @@ -585,7 +585,7 @@ namespace FourSlash { } private getGoToDefinition(): ts.DefinitionInfo[] { - return this.languageService.getDefinitionAtPosition(this.activeFile.fileName, this.currentCaretPosition) + return this.languageService.getDefinitionAtPosition(this.activeFile.fileName, this.currentCaretPosition); } public verifyGoToType(arg0: any, endMarkerNames?: string | string[]) { @@ -926,7 +926,7 @@ namespace FourSlash { function rangeToReferenceEntry(r: Range) { let { isWriteAccess, isDefinition } = (r.marker && r.marker.data) || { isWriteAccess: false, isDefinition: false }; isWriteAccess = !!isWriteAccess; isDefinition = !!isDefinition; - return { fileName: r.fileName, textSpan: { start: r.start, length: r.end - r.start }, isWriteAccess, isDefinition } + return { fileName: r.fileName, textSpan: { start: r.start, length: r.end - r.start }, isWriteAccess, isDefinition }; } } @@ -2136,7 +2136,7 @@ namespace FourSlash { const result = includeWhiteSpace ? actualText === expectedText - : this.removeWhitespace(actualText) === this.removeWhitespace(expectedText) + : this.removeWhitespace(actualText) === this.removeWhitespace(expectedText); if (!result) { this.raiseError(`Actual text doesn't match expected text. Actual:\n'${actualText}'\nExpected:\n'${expectedText}'`); @@ -2173,7 +2173,7 @@ namespace FourSlash { start: diagnostic.start, length: diagnostic.length, code: diagnostic.code - } + }; }); const dedupedDiagnositcs = ts.deduplicate(diagnosticsForCodeFix, ts.equalOwnProperties); diff --git a/src/harness/harness.ts b/src/harness/harness.ts index a20c6229f54..b8a4360de4f 100644 --- a/src/harness/harness.ts +++ b/src/harness/harness.ts @@ -44,7 +44,7 @@ declare namespace NodeJS { declare var window: {}; declare var XMLHttpRequest: { new(): XMLHttpRequest; -} +}; interface XMLHttpRequest { readonly readyState: number; readonly responseText: string; @@ -1017,7 +1017,7 @@ namespace Harness { } else { if (!es6TestLibFileNameSourceFileMap.get(libFileName)) { - es6TestLibFileNameSourceFileMap.set(libFileName, createSourceFileAndAssertInvariants(libFileName, IO.readFile(libFileName), scriptTarget)) + es6TestLibFileNameSourceFileMap.set(libFileName, createSourceFileAndAssertInvariants(libFileName, IO.readFile(libFileName), scriptTarget)); } } } diff --git a/src/harness/harnessLanguageService.ts b/src/harness/harnessLanguageService.ts index 202b429fcbc..e7f32748570 100644 --- a/src/harness/harnessLanguageService.ts +++ b/src/harness/harnessLanguageService.ts @@ -779,7 +779,7 @@ namespace Harness.LanguageService { start: 0 }); return prev; - } + }; return proxy; } }), diff --git a/src/harness/unittests/compileOnSave.ts b/src/harness/unittests/compileOnSave.ts index 48558b5c1a3..114ee77fef1 100644 --- a/src/harness/unittests/compileOnSave.ts +++ b/src/harness/unittests/compileOnSave.ts @@ -496,7 +496,7 @@ namespace ts.projectSystem { const emitOutput = host.readFile(path + ".js"); assert.equal(emitOutput, f.content + newLine, "content of emit output should be identical with the input + newline"); } - }) + }); it("should emit specified file", () => { const file1 = { diff --git a/src/harness/unittests/printer.ts b/src/harness/unittests/printer.ts index 2496a880bc5..1a5a5a12f9e 100644 --- a/src/harness/unittests/printer.ts +++ b/src/harness/unittests/printer.ts @@ -9,7 +9,7 @@ namespace ts { Harness.Baseline.runBaseline(`printerApi/${prefix}.${name}.js`, () => printCallback(createPrinter({ newLine: NewLineKind.CarriageReturnLineFeed, ...options }))); }); - } + }; } describe("printFile", () => { diff --git a/src/harness/unittests/textStorage.ts b/src/harness/unittests/textStorage.ts index b4287f2610c..545d090df9b 100644 --- a/src/harness/unittests/textStorage.ts +++ b/src/harness/unittests/textStorage.ts @@ -65,6 +65,6 @@ namespace ts.textStorage { ts1.getLineInfo(0); assert.isTrue(ts1.hasScriptVersionCache(), "have script version cache - 2"); - }) + }); }); } \ No newline at end of file diff --git a/src/harness/unittests/tsserverProjectSystem.ts b/src/harness/unittests/tsserverProjectSystem.ts index 7446cc84deb..1531b18bb9d 100644 --- a/src/harness/unittests/tsserverProjectSystem.ts +++ b/src/harness/unittests/tsserverProjectSystem.ts @@ -628,7 +628,7 @@ namespace ts.projectSystem { checkProjectActualFiles(service.configuredProjects[0], []); checkProjectActualFiles(service.inferredProjects[0], [f1.path]); - }) + }); it("create configured project without file list", () => { const configFile: FileOrFolder = { @@ -1181,7 +1181,7 @@ namespace ts.projectSystem { const host = createServerHost([f1, f2, libFile]); const service = createProjectService(host); - service.openExternalProject({ projectFileName: "/a/b/project", rootFiles: toExternalFiles([f1.path, f2.path]), options: {} }) + service.openExternalProject({ projectFileName: "/a/b/project", rootFiles: toExternalFiles([f1.path, f2.path]), options: {} }); service.openClientFile(f1.path); service.openClientFile(f2.path, "let x: string"); @@ -1213,7 +1213,7 @@ namespace ts.projectSystem { const host = createServerHost([f1, f2, libFile]); const service = createProjectService(host); - service.openExternalProject({ projectFileName: "/a/b/project", rootFiles: [{ fileName: f1.path }, { fileName: f2.path, hasMixedContent: true }], options: {} }) + service.openExternalProject({ projectFileName: "/a/b/project", rootFiles: [{ fileName: f1.path }, { fileName: f2.path, hasMixedContent: true }], options: {} }); service.openClientFile(f1.path); service.openClientFile(f2.path, "let somelongname: string"); @@ -2040,7 +2040,7 @@ namespace ts.projectSystem { for (const f of [f2, f3]) { const scriptInfo = projectService.getScriptInfoForNormalizedPath(server.toNormalizedPath(f.path)); - assert.equal(scriptInfo.containingProjects.length, 0, `expect 0 containing projects for '${f.path}'`) + assert.equal(scriptInfo.containingProjects.length, 0, `expect 0 containing projects for '${f.path}'`); } }); @@ -2156,7 +2156,7 @@ namespace ts.projectSystem { projectFileName, rootFiles: [toExternalFile(f1.path)], options: {} - }) + }); projectService.openClientFile(f1.path, "let x = 1;\nlet y = 2;"); projectService.checkNumberOfProjects({ externalProjects: 1 }); @@ -3307,12 +3307,12 @@ namespace ts.projectSystem { isCancellationRequested: () => false, setRequest: requestId => { if (expectedRequestId === undefined) { - assert.isTrue(false, "unexpected call") + assert.isTrue(false, "unexpected call"); } assert.equal(requestId, expectedRequestId); }, resetRequest: noop - } + }; const session = createSession(host, /*typingsInstaller*/ undefined, /*projectServiceEventHandler*/ undefined, cancellationToken); expectedRequestId = session.getNextSeq(); @@ -3359,13 +3359,13 @@ namespace ts.projectSystem { currentId = requestId; }, resetRequest(requestId) { - assert.equal(requestId, currentId, "unexpected request id in cancellation") + assert.equal(requestId, currentId, "unexpected request id in cancellation"); currentId = undefined; }, isCancellationRequested() { return requestToCancel === currentId; } - } + }; })(); const host = createServerHost([f1, config]); const session = createSession(host, /*typingsInstaller*/ undefined, () => {}, cancellationToken); diff --git a/src/harness/unittests/typingsInstaller.ts b/src/harness/unittests/typingsInstaller.ts index 29076df80f2..2eae01c3520 100644 --- a/src/harness/unittests/typingsInstaller.ts +++ b/src/harness/unittests/typingsInstaller.ts @@ -56,7 +56,7 @@ namespace ts.projectSystem { path: "/a/config.js", content: "export let x = 1" }; - const typesCache = "/cache" + const typesCache = "/cache"; const typesConfig = { path: typesCache + "/node_modules/@types/config/index.d.ts", content: "export let y: number;" @@ -74,7 +74,7 @@ namespace ts.projectSystem { super(host, { typesRegistry: createTypesRegistry("config"), globalTypingsCacheLocation: typesCache }); } installWorker(_requestId: number, _args: string[], _cwd: string, _cb: server.typingsInstaller.RequestCompletedAction) { - assert(false, "should not be called") + assert(false, "should not be called"); } })(); const service = createProjectService(host, { typingsInstaller: installer }); diff --git a/src/server/editorServices.ts b/src/server/editorServices.ts index d0535e2f528..cbbe23650fb 100644 --- a/src/server/editorServices.ts +++ b/src/server/editorServices.ts @@ -140,7 +140,7 @@ namespace ts.server { getScriptKind: _ => undefined, hasMixedContent: (fileName, extraFileExtensions) => { const mixedContentExtensions = ts.map(ts.filter(extraFileExtensions, item => item.isMixedContent), item => item.extension); - return forEach(mixedContentExtensions, extension => fileExtensionIs(fileName, extension)) + return forEach(mixedContentExtensions, extension => fileExtensionIs(fileName, extension)); } }; @@ -1377,7 +1377,7 @@ namespace ts.server { // close projects that were missing in the input list forEachKey(projectsToClose, externalProjectName => { - this.closeExternalProject(externalProjectName, /*suppressRefresh*/ true) + this.closeExternalProject(externalProjectName, /*suppressRefresh*/ true); }); this.refreshInferredProjects(); diff --git a/src/server/project.ts b/src/server/project.ts index e4669ea191b..339f0288688 100644 --- a/src/server/project.ts +++ b/src/server/project.ts @@ -723,7 +723,7 @@ namespace ts.server { const fileName = resolvedTypeReferenceDirective.resolvedFileName; const typeFilePath = toPath(fileName, currentDirectory, getCanonicalFileName); referencedFiles.set(typeFilePath, true); - }) + }); } const allFileNames = arrayFrom(referencedFiles.keys()) as Path[]; @@ -745,7 +745,7 @@ namespace ts.server { const id = nextId; nextId++; return makeInferredProjectName(id); - } + }; })(); private _isJsInferredProject = false; diff --git a/src/server/scriptInfo.ts b/src/server/scriptInfo.ts index be3ef34ce3a..e5df91fd930 100644 --- a/src/server/scriptInfo.ts +++ b/src/server/scriptInfo.ts @@ -48,7 +48,7 @@ namespace ts.server { public reloadFromFile(tempFileName?: string) { if (this.svc || (tempFileName !== this.fileName)) { - this.reload(this.getFileText(tempFileName)) + this.reload(this.getFileText(tempFileName)); } else { this.setText(undefined); diff --git a/src/server/server.ts b/src/server/server.ts index 2a5990f4252..2c68963ec5c 100644 --- a/src/server/server.ts +++ b/src/server/server.ts @@ -47,14 +47,14 @@ namespace ts.server { if (process.env.XDG_CACHE_HOME) { return process.env.XDG_CACHE_HOME; } - const usersDir = platformIsDarwin ? "Users" : "home" + const usersDir = platformIsDarwin ? "Users" : "home"; const homePath = (os.homedir && os.homedir()) || process.env.HOME || ((process.env.LOGNAME || process.env.USER) && `/${usersDir}/${process.env.LOGNAME || process.env.USER}`) || os.tmpdir(); const cacheFolder = platformIsDarwin ? "Library/Caches" - : ".cache" + : ".cache"; return combinePaths(normalizeSlashes(homePath), cacheFolder); } @@ -653,7 +653,7 @@ namespace ts.server { // this drive is unsafe - return no-op watcher return { close() { } }; } - } + }; } // Override sys.write because fs.writeSync is not reliable on Node 4 diff --git a/src/server/session.ts b/src/server/session.ts index f1b373c4a90..b5e7e44970d 100644 --- a/src/server/session.ts +++ b/src/server/session.ts @@ -226,7 +226,7 @@ namespace ts.server { /** * Represents operation that can schedule its next step to be executed later. - * Scheduling is done via instance of NextStep. If on current step subsequent step was not scheduled - operation is assumed to be completed. + * Scheduling is done via instance of NextStep. If on current step subsequent step was not scheduled - operation is assumed to be completed. */ class MultistepOperation { private requestId: number; @@ -239,7 +239,7 @@ namespace ts.server { this.next = { immediate: action => this.immediate(action), delay: (ms, action) => this.delay(ms, action) - } + }; } public startNew(action: (next: NextStep) => void) { @@ -262,7 +262,7 @@ namespace ts.server { private immediate(action: () => void) { const requestId = this.requestId; - Debug.assert(requestId === this.operationHost.getCurrentRequestId(), "immediate: incorrect request id") + Debug.assert(requestId === this.operationHost.getCurrentRequestId(), "immediate: incorrect request id"); this.setImmediateId(this.operationHost.getServerHost().setImmediate(() => { this.immediateId = undefined; this.operationHost.executeWithRequestId(requestId, () => this.executeAction(action)); @@ -271,7 +271,7 @@ namespace ts.server { private delay(ms: number, action: () => void) { const requestId = this.requestId; - Debug.assert(requestId === this.operationHost.getCurrentRequestId(), "delay: incorrect request id") + Debug.assert(requestId === this.operationHost.getCurrentRequestId(), "delay: incorrect request id"); this.setTimerHandle(this.operationHost.getServerHost().setTimeout(() => { this.timerHandle = undefined; this.operationHost.executeWithRequestId(requestId, () => this.executeAction(action)); @@ -351,7 +351,7 @@ namespace ts.server { logError: (err, cmd) => this.logError(err, cmd), sendRequestCompletedEvent: requestId => this.sendRequestCompletedEvent(requestId), isCancellationRequested: () => cancellationToken.isCancellationRequested() - } + }; this.errorCheck = new MultistepOperation(multistepOperationHost); this.projectService = new ProjectService(host, logger, cancellationToken, useSingleInferredProject, typingsInstaller, this.eventHander); this.gcTimer = new GcTimer(host, /*delay*/ 7000, logger); diff --git a/src/server/typingsInstaller/nodeTypingsInstaller.ts b/src/server/typingsInstaller/nodeTypingsInstaller.ts index ff20e89e2d7..447a2c05429 100644 --- a/src/server/typingsInstaller/nodeTypingsInstaller.ts +++ b/src/server/typingsInstaller/nodeTypingsInstaller.ts @@ -61,9 +61,7 @@ namespace ts.server.typingsInstaller { return combinePaths(normalizeSlashes(globalTypingsCacheLocation), `node_modules/${TypesRegistryPackageName}/index.json`); } - type ExecSync = { - (command: string, options: { cwd: string, stdio?: "ignore" }): any - } + type ExecSync = (command: string, options: { cwd: string, stdio?: "ignore" }) => any; export class NodeTypingsInstaller extends TypingsInstaller { private readonly execSync: ExecSync; diff --git a/src/server/watchGuard/watchGuard.ts b/src/server/watchGuard/watchGuard.ts index 7a0307a9120..f57369aecb9 100644 --- a/src/server/watchGuard/watchGuard.ts +++ b/src/server/watchGuard/watchGuard.ts @@ -11,7 +11,7 @@ const fs: { watch(directoryName: string, options: any, callback: () => {}): any // This means that here we treat any result (success or exception) from fs.watch as success since it does not tear down the process. // The only case that should be considered as failure - when watchGuard process crashes. try { - const watcher = fs.watch(directoryName, { recursive: true }, () => ({})) + const watcher = fs.watch(directoryName, { recursive: true }, () => ({})); watcher.close(); } catch (_e) { diff --git a/src/services/codefixes/importFixes.ts b/src/services/codefixes/importFixes.ts index 1f1b499d03b..1c157257051 100644 --- a/src/services/codefixes/importFixes.ts +++ b/src/services/codefixes/importFixes.ts @@ -3,8 +3,8 @@ namespace ts.codefix { type ImportCodeActionKind = "CodeChange" | "InsertingIntoExistingImport" | "NewImport"; interface ImportCodeAction extends CodeAction { - kind: ImportCodeActionKind, - moduleSpecifier?: string + kind: ImportCodeActionKind; + moduleSpecifier?: string; } enum ModuleSpecifierComparison { @@ -75,7 +75,7 @@ namespace ts.codefix { getAllActions() { let result: ImportCodeAction[] = []; for (const key in this.symbolIdToActionMap) { - result = concatenate(result, this.symbolIdToActionMap[key]) + result = concatenate(result, this.symbolIdToActionMap[key]); } return result; } diff --git a/src/services/completions.ts b/src/services/completions.ts index 6a4ee3485de..525f1a8416c 100644 --- a/src/services/completions.ts +++ b/src/services/completions.ts @@ -1,10 +1,12 @@ +/// + /* @internal */ namespace ts.Completions { export type Log = (message: string) => void; export function getCompletionsAtPosition(host: LanguageServiceHost, typeChecker: TypeChecker, log: Log, compilerOptions: CompilerOptions, sourceFile: SourceFile, position: number): CompletionInfo | undefined { if (isInReferenceComment(sourceFile, position)) { - return getTripleSlashReferenceCompletion(sourceFile, position, compilerOptions, host); + return PathCompletions.getTripleSlashReferenceCompletion(sourceFile, position, compilerOptions, host); } if (isInString(sourceFile, position)) { @@ -176,7 +178,7 @@ namespace ts.Completions { // i.e. import * as ns from "/*completion position*/"; // import x = require("/*completion position*/"); // var y = require("/*completion position*/"); - return getStringLiteralCompletionEntriesFromModuleNames(node, compilerOptions, host, typeChecker); + return PathCompletions.getStringLiteralCompletionEntriesFromModuleNames(node, compilerOptions, host, typeChecker); } else if (isEqualityExpression(node.parent)) { // Get completions from the type of the other operand @@ -278,489 +280,6 @@ namespace ts.Completions { } } - function getStringLiteralCompletionEntriesFromModuleNames(node: StringLiteral, compilerOptions: CompilerOptions, host: LanguageServiceHost, typeChecker: TypeChecker): CompletionInfo { - const literalValue = normalizeSlashes(node.text); - - const scriptPath = node.getSourceFile().path; - const scriptDirectory = getDirectoryPath(scriptPath); - - const span = getDirectoryFragmentTextSpan((node).text, node.getStart() + 1); - let entries: CompletionEntry[]; - if (isPathRelativeToScript(literalValue) || isRootedDiskPath(literalValue)) { - const extensions = getSupportedExtensions(compilerOptions); - if (compilerOptions.rootDirs) { - entries = getCompletionEntriesForDirectoryFragmentWithRootDirs( - compilerOptions.rootDirs, literalValue, scriptDirectory, extensions, /*includeExtensions*/false, span, compilerOptions, host, scriptPath); - } - else { - entries = getCompletionEntriesForDirectoryFragment( - literalValue, scriptDirectory, extensions, /*includeExtensions*/false, span, host, scriptPath); - } - } - else { - // Check for node modules - entries = getCompletionEntriesForNonRelativeModules(literalValue, scriptDirectory, span, compilerOptions, host, typeChecker); - } - return { - isGlobalCompletion: false, - isMemberCompletion: false, - isNewIdentifierLocation: true, - entries - }; - } - - /** - * Takes a script path and returns paths for all potential folders that could be merged with its - * containing folder via the "rootDirs" compiler option - */ - function getBaseDirectoriesFromRootDirs(rootDirs: string[], basePath: string, scriptPath: string, ignoreCase: boolean): string[] { - // Make all paths absolute/normalized if they are not already - rootDirs = map(rootDirs, rootDirectory => normalizePath(isRootedDiskPath(rootDirectory) ? rootDirectory : combinePaths(basePath, rootDirectory))); - - // Determine the path to the directory containing the script relative to the root directory it is contained within - let relativeDirectory: string; - for (const rootDirectory of rootDirs) { - if (containsPath(rootDirectory, scriptPath, basePath, ignoreCase)) { - relativeDirectory = scriptPath.substr(rootDirectory.length); - break; - } - } - - // Now find a path for each potential directory that is to be merged with the one containing the script - return deduplicate(map(rootDirs, rootDirectory => combinePaths(rootDirectory, relativeDirectory))); - } - - function getCompletionEntriesForDirectoryFragmentWithRootDirs(rootDirs: string[], fragment: string, scriptPath: string, extensions: string[], includeExtensions: boolean, span: TextSpan, compilerOptions: CompilerOptions, host: LanguageServiceHost, exclude?: string): CompletionEntry[] { - const basePath = compilerOptions.project || host.getCurrentDirectory(); - const ignoreCase = !(host.useCaseSensitiveFileNames && host.useCaseSensitiveFileNames()); - const baseDirectories = getBaseDirectoriesFromRootDirs(rootDirs, basePath, scriptPath, ignoreCase); - - const result: CompletionEntry[] = []; - - for (const baseDirectory of baseDirectories) { - getCompletionEntriesForDirectoryFragment(fragment, baseDirectory, extensions, includeExtensions, span, host, exclude, result); - } - - return result; - } - - /** - * Given a path ending at a directory, gets the completions for the path, and filters for those entries containing the basename. - */ - function getCompletionEntriesForDirectoryFragment(fragment: string, scriptPath: string, extensions: string[], includeExtensions: boolean, span: TextSpan, host: LanguageServiceHost, exclude?: string, result: CompletionEntry[] = []): CompletionEntry[] { - if (fragment === undefined) { - fragment = ""; - } - - fragment = normalizeSlashes(fragment); - - /** - * Remove the basename from the path. Note that we don't use the basename to filter completions; - * the client is responsible for refining completions. - */ - fragment = getDirectoryPath(fragment); - - if (fragment === "") { - fragment = "." + directorySeparator; - } - - fragment = ensureTrailingDirectorySeparator(fragment); - - const absolutePath = normalizeAndPreserveTrailingSlash(isRootedDiskPath(fragment) ? fragment : combinePaths(scriptPath, fragment)); - const baseDirectory = getDirectoryPath(absolutePath); - const ignoreCase = !(host.useCaseSensitiveFileNames && host.useCaseSensitiveFileNames()); - - if (tryDirectoryExists(host, baseDirectory)) { - // Enumerate the available files if possible - const files = tryReadDirectory(host, baseDirectory, extensions, /*exclude*/undefined, /*include*/["./*"]); - - if (files) { - /** - * Multiple file entries might map to the same truncated name once we remove extensions - * (happens iff includeExtensions === false)so we use a set-like data structure. Eg: - * - * both foo.ts and foo.tsx become foo - */ - const foundFiles = createMap(); - for (let filePath of files) { - filePath = normalizePath(filePath); - if (exclude && comparePaths(filePath, exclude, scriptPath, ignoreCase) === Comparison.EqualTo) { - continue; - } - - const foundFileName = includeExtensions ? getBaseFileName(filePath) : removeFileExtension(getBaseFileName(filePath)); - - if (!foundFiles.get(foundFileName)) { - foundFiles.set(foundFileName, true); - } - } - - forEachKey(foundFiles, foundFile => { - result.push(createCompletionEntryForModule(foundFile, ScriptElementKind.scriptElement, span)); - }); - } - - // If possible, get folder completion as well - const directories = tryGetDirectories(host, baseDirectory); - - if (directories) { - for (const directory of directories) { - const directoryName = getBaseFileName(normalizePath(directory)); - - result.push(createCompletionEntryForModule(directoryName, ScriptElementKind.directory, span)); - } - } - } - - return result; - } - - /** - * Check all of the declared modules and those in node modules. Possible sources of modules: - * Modules that are found by the type checker - * Modules found relative to "baseUrl" compliler options (including patterns from "paths" compiler option) - * Modules from node_modules (i.e. those listed in package.json) - * This includes all files that are found in node_modules/moduleName/ with acceptable file extensions - */ - function getCompletionEntriesForNonRelativeModules(fragment: string, scriptPath: string, span: TextSpan, compilerOptions: CompilerOptions, host: LanguageServiceHost, typeChecker: TypeChecker): CompletionEntry[] { - const { baseUrl, paths } = compilerOptions; - - let result: CompletionEntry[]; - - if (baseUrl) { - const fileExtensions = getSupportedExtensions(compilerOptions); - const projectDir = compilerOptions.project || host.getCurrentDirectory(); - const absolute = isRootedDiskPath(baseUrl) ? baseUrl : combinePaths(projectDir, baseUrl); - result = getCompletionEntriesForDirectoryFragment(fragment, normalizePath(absolute), fileExtensions, /*includeExtensions*/false, span, host); - - if (paths) { - for (const path in paths) { - if (paths.hasOwnProperty(path)) { - if (path === "*") { - if (paths[path]) { - for (const pattern of paths[path]) { - for (const match of getModulesForPathsPattern(fragment, baseUrl, pattern, fileExtensions, host)) { - result.push(createCompletionEntryForModule(match, ScriptElementKind.externalModuleName, span)); - } - } - } - } - else if (startsWith(path, fragment)) { - const entry = paths[path] && paths[path].length === 1 && paths[path][0]; - if (entry) { - result.push(createCompletionEntryForModule(path, ScriptElementKind.externalModuleName, span)); - } - } - } - } - } - } - else { - result = []; - } - - getCompletionEntriesFromTypings(host, compilerOptions, scriptPath, span, result); - - for (const moduleName of enumeratePotentialNonRelativeModules(fragment, scriptPath, compilerOptions, typeChecker, host)) { - result.push(createCompletionEntryForModule(moduleName, ScriptElementKind.externalModuleName, span)); - } - - return result; - } - - function getModulesForPathsPattern(fragment: string, baseUrl: string, pattern: string, fileExtensions: string[], host: LanguageServiceHost): string[] { - if (host.readDirectory) { - const parsed = hasZeroOrOneAsteriskCharacter(pattern) ? tryParsePattern(pattern) : undefined; - if (parsed) { - // The prefix has two effective parts: the directory path and the base component after the filepath that is not a - // full directory component. For example: directory/path/of/prefix/base* - const normalizedPrefix = normalizeAndPreserveTrailingSlash(parsed.prefix); - const normalizedPrefixDirectory = getDirectoryPath(normalizedPrefix); - const normalizedPrefixBase = getBaseFileName(normalizedPrefix); - - const fragmentHasPath = fragment.indexOf(directorySeparator) !== -1; - - // Try and expand the prefix to include any path from the fragment so that we can limit the readDirectory call - const expandedPrefixDirectory = fragmentHasPath ? combinePaths(normalizedPrefixDirectory, normalizedPrefixBase + getDirectoryPath(fragment)) : normalizedPrefixDirectory; - - const normalizedSuffix = normalizePath(parsed.suffix); - const baseDirectory = combinePaths(baseUrl, expandedPrefixDirectory); - const completePrefix = fragmentHasPath ? baseDirectory : ensureTrailingDirectorySeparator(baseDirectory) + normalizedPrefixBase; - - // If we have a suffix, then we need to read the directory all the way down. We could create a glob - // that encodes the suffix, but we would have to escape the character "?" which readDirectory - // doesn't support. For now, this is safer but slower - const includeGlob = normalizedSuffix ? "**/*" : "./*"; - - const matches = tryReadDirectory(host, baseDirectory, fileExtensions, undefined, [includeGlob]); - if (matches) { - const result: string[] = []; - - // Trim away prefix and suffix - for (const match of matches) { - const normalizedMatch = normalizePath(match); - if (!endsWith(normalizedMatch, normalizedSuffix) || !startsWith(normalizedMatch, completePrefix)) { - continue; - } - - const start = completePrefix.length; - const length = normalizedMatch.length - start - normalizedSuffix.length; - - result.push(removeFileExtension(normalizedMatch.substr(start, length))); - } - return result; - } - } - } - - return undefined; - } - - function enumeratePotentialNonRelativeModules(fragment: string, scriptPath: string, options: CompilerOptions, typeChecker: TypeChecker, host: LanguageServiceHost): string[] { - // Check If this is a nested module - const isNestedModule = fragment.indexOf(directorySeparator) !== -1; - const moduleNameFragment = isNestedModule ? fragment.substr(0, fragment.lastIndexOf(directorySeparator)) : undefined; - - // Get modules that the type checker picked up - const ambientModules = map(typeChecker.getAmbientModules(), sym => stripQuotes(sym.name)); - let nonRelativeModules = filter(ambientModules, moduleName => startsWith(moduleName, fragment)); - - // Nested modules of the form "module-name/sub" need to be adjusted to only return the string - // after the last '/' that appears in the fragment because that's where the replacement span - // starts - if (isNestedModule) { - const moduleNameWithSeperator = ensureTrailingDirectorySeparator(moduleNameFragment); - nonRelativeModules = map(nonRelativeModules, moduleName => { - if (startsWith(fragment, moduleNameWithSeperator)) { - return moduleName.substr(moduleNameWithSeperator.length); - } - return moduleName; - }); - } - - - if (!options.moduleResolution || options.moduleResolution === ModuleResolutionKind.NodeJs) { - for (const visibleModule of enumerateNodeModulesVisibleToScript(host, scriptPath)) { - if (!isNestedModule) { - nonRelativeModules.push(visibleModule.moduleName); - } - else if (startsWith(visibleModule.moduleName, moduleNameFragment)) { - const nestedFiles = tryReadDirectory(host, visibleModule.moduleDir, supportedTypeScriptExtensions, /*exclude*/undefined, /*include*/["./*"]); - if (nestedFiles) { - for (let f of nestedFiles) { - f = normalizePath(f); - const nestedModule = removeFileExtension(getBaseFileName(f)); - nonRelativeModules.push(nestedModule); - } - } - } - } - } - - return deduplicate(nonRelativeModules); - } - - function getTripleSlashReferenceCompletion(sourceFile: SourceFile, position: number, compilerOptions: CompilerOptions, host: LanguageServiceHost): CompletionInfo { - const token = getTokenAtPosition(sourceFile, position); - if (!token) { - return undefined; - } - const commentRanges: CommentRange[] = getLeadingCommentRanges(sourceFile.text, token.pos); - - if (!commentRanges || !commentRanges.length) { - return undefined; - } - - const range = forEach(commentRanges, commentRange => position >= commentRange.pos && position <= commentRange.end && commentRange); - - if (!range) { - return undefined; - } - - const completionInfo: CompletionInfo = { - /** - * We don't want the editor to offer any other completions, such as snippets, inside a comment. - */ - isGlobalCompletion: false, - isMemberCompletion: false, - /** - * The user may type in a path that doesn't yet exist, creating a "new identifier" - * with respect to the collection of identifiers the server is aware of. - */ - isNewIdentifierLocation: true, - - entries: [] - }; - - const text = sourceFile.text.substr(range.pos, position - range.pos); - - const match = tripleSlashDirectiveFragmentRegex.exec(text); - - if (match) { - const prefix = match[1]; - const kind = match[2]; - const toComplete = match[3]; - - const scriptPath = getDirectoryPath(sourceFile.path); - if (kind === "path") { - // Give completions for a relative path - const span: TextSpan = getDirectoryFragmentTextSpan(toComplete, range.pos + prefix.length); - completionInfo.entries = getCompletionEntriesForDirectoryFragment(toComplete, scriptPath, getSupportedExtensions(compilerOptions), /*includeExtensions*/true, span, host, sourceFile.path); - } - else { - // Give completions based on the typings available - const span: TextSpan = { start: range.pos + prefix.length, length: match[0].length - prefix.length }; - completionInfo.entries = getCompletionEntriesFromTypings(host, compilerOptions, scriptPath, span); - } - } - - return completionInfo; - } - - function getCompletionEntriesFromTypings(host: LanguageServiceHost, options: CompilerOptions, scriptPath: string, span: TextSpan, result: CompletionEntry[] = []): CompletionEntry[] { - // Check for typings specified in compiler options - if (options.types) { - for (const moduleName of options.types) { - result.push(createCompletionEntryForModule(moduleName, ScriptElementKind.externalModuleName, span)); - } - } - else if (host.getDirectories) { - let typeRoots: string[]; - try { - // Wrap in try catch because getEffectiveTypeRoots touches the filesystem - typeRoots = getEffectiveTypeRoots(options, host); - } - catch (e) {} - - if (typeRoots) { - for (const root of typeRoots) { - getCompletionEntriesFromDirectories(host, root, span, result); - } - } - } - - if (host.getDirectories) { - // Also get all @types typings installed in visible node_modules directories - for (const packageJson of findPackageJsons(scriptPath, host)) { - const typesDir = combinePaths(getDirectoryPath(packageJson), "node_modules/@types"); - getCompletionEntriesFromDirectories(host, typesDir, span, result); - } - } - - return result; - } - - function getCompletionEntriesFromDirectories(host: LanguageServiceHost, directory: string, span: TextSpan, result: Push) { - if (host.getDirectories && tryDirectoryExists(host, directory)) { - const directories = tryGetDirectories(host, directory); - if (directories) { - for (let typeDirectory of directories) { - typeDirectory = normalizePath(typeDirectory); - result.push(createCompletionEntryForModule(getBaseFileName(typeDirectory), ScriptElementKind.externalModuleName, span)); - } - } - } - } - - function findPackageJsons(currentDir: string, host: LanguageServiceHost): string[] { - const paths: string[] = []; - let currentConfigPath: string; - while (true) { - currentConfigPath = findConfigFile(currentDir, (f) => tryFileExists(host, f), "package.json"); - if (currentConfigPath) { - paths.push(currentConfigPath); - - currentDir = getDirectoryPath(currentConfigPath); - const parent = getDirectoryPath(currentDir); - if (currentDir === parent) { - break; - } - currentDir = parent; - } - else { - break; - } - } - - return paths; - } - - function enumerateNodeModulesVisibleToScript(host: LanguageServiceHost, scriptPath: string) { - const result: VisibleModuleInfo[] = []; - - if (host.readFile && host.fileExists) { - for (const packageJson of findPackageJsons(scriptPath, host)) { - const contents = tryReadingPackageJson(packageJson); - if (!contents) { - return; - } - - const nodeModulesDir = combinePaths(getDirectoryPath(packageJson), "node_modules"); - const foundModuleNames: string[] = []; - - // Provide completions for all non @types dependencies - for (const key of nodeModulesDependencyKeys) { - addPotentialPackageNames(contents[key], foundModuleNames); - } - - for (const moduleName of foundModuleNames) { - const moduleDir = combinePaths(nodeModulesDir, moduleName); - result.push({ - moduleName, - moduleDir - }); - } - } - } - - return result; - - function tryReadingPackageJson(filePath: string) { - try { - const fileText = tryReadFile(host, filePath); - return fileText ? JSON.parse(fileText) : undefined; - } - catch (e) { - return undefined; - } - } - - function addPotentialPackageNames(dependencies: any, result: string[]) { - if (dependencies) { - for (const dep in dependencies) { - if (dependencies.hasOwnProperty(dep) && !startsWith(dep, "@types/")) { - result.push(dep); - } - } - } - } - } - - function createCompletionEntryForModule(name: string, kind: string, replacementSpan: TextSpan): CompletionEntry { - return { name, kind, kindModifiers: ScriptElementKindModifier.none, sortText: name, replacementSpan }; - } - - // Replace everything after the last directory seperator that appears - function getDirectoryFragmentTextSpan(text: string, textStart: number): TextSpan { - const index = text.lastIndexOf(directorySeparator); - const offset = index !== -1 ? index + 1 : 0; - return { start: textStart + offset, length: text.length - offset }; - } - - // Returns true if the path is explicitly relative to the script (i.e. relative to . or ..) - function isPathRelativeToScript(path: string) { - if (path && path.length >= 2 && path.charCodeAt(0) === CharacterCodes.dot) { - const slashIndex = path.length >= 3 && path.charCodeAt(1) === CharacterCodes.dot ? 2 : 1; - const slashCharCode = path.charCodeAt(slashIndex); - return slashCharCode === CharacterCodes.slash || slashCharCode === CharacterCodes.backslash; - } - return false; - } - - function normalizeAndPreserveTrailingSlash(path: string) { - return hasTrailingDirectorySeparator(path) ? ensureTrailingDirectorySeparator(normalizePath(path)) : normalizePath(path); - } - export function getCompletionEntryDetails(typeChecker: TypeChecker, log: (message: string) => void, compilerOptions: CompilerOptions, sourceFile: SourceFile, position: number, entryName: string): CompletionEntryDetails { // Compute all the completion symbols again. const completionData = getCompletionData(typeChecker, log, sourceFile, position); @@ -1487,20 +1006,18 @@ namespace ts.Completions { } function isFunction(kind: SyntaxKind): boolean { + if (!isFunctionLikeKind(kind)) { + return false; + } + switch (kind) { - case SyntaxKind.FunctionExpression: - case SyntaxKind.ArrowFunction: - case SyntaxKind.FunctionDeclaration: - case SyntaxKind.MethodDeclaration: - case SyntaxKind.MethodSignature: - case SyntaxKind.GetAccessor: - case SyntaxKind.SetAccessor: - case SyntaxKind.CallSignature: - case SyntaxKind.ConstructSignature: - case SyntaxKind.IndexSignature: + case SyntaxKind.Constructor: + case SyntaxKind.ConstructorType: + case SyntaxKind.FunctionType: + return false; + default: return true; } - return false; } /** @@ -1778,59 +1295,6 @@ namespace ts.Completions { }); } - /** - * Matches a triple slash reference directive with an incomplete string literal for its path. Used - * to determine if the caret is currently within the string literal and capture the literal fragment - * for completions. - * For example, this matches - * - * /// (host: LanguageServiceHost, toApply: (...a: any[]) => T, ...args: any[]) { - try { - return toApply && toApply.apply(host, args); - } - catch (e) {} - return undefined; - } - function isEqualityExpression(node: Node): node is BinaryExpression { return isBinaryExpression(node) && isEqualityOperatorKind(node.operatorToken.kind); } diff --git a/src/services/findAllReferences.ts b/src/services/findAllReferences.ts index 3dd55eb3590..b6a8f5b5d23 100644 --- a/src/services/findAllReferences.ts +++ b/src/services/findAllReferences.ts @@ -422,7 +422,7 @@ namespace ts.FindAllReferences { name, textSpan: references[0].textSpan, displayParts: [{ text: name, kind: ScriptElementKind.keyword }] - } + }; return [{ definition, references }]; } @@ -613,7 +613,7 @@ namespace ts.FindAllReferences { const result: Node[] = []; for (const decl of classSymbol.members.get("__constructor").declarations) { - const ctrKeyword = ts.findChildOfKind(decl, ts.SyntaxKind.ConstructorKeyword, sourceFile)! + const ctrKeyword = ts.findChildOfKind(decl, ts.SyntaxKind.ConstructorKeyword, sourceFile)!; Debug.assert(decl.kind === SyntaxKind.Constructor && !!ctrKeyword); result.push(ctrKeyword); } diff --git a/src/services/jsDoc.ts b/src/services/jsDoc.ts index 59c6ad4b03b..a2a32f95fa4 100644 --- a/src/services/jsDoc.ts +++ b/src/services/jsDoc.ts @@ -107,7 +107,7 @@ namespace ts.JsDoc { kind: ScriptElementKind.keyword, kindModifiers: "", sortText: "0" - } + }; })); } diff --git a/src/services/pathCompletions.ts b/src/services/pathCompletions.ts new file mode 100644 index 00000000000..96597da9d2d --- /dev/null +++ b/src/services/pathCompletions.ts @@ -0,0 +1,538 @@ +/* @internal */ +namespace ts.Completions.PathCompletions { + export function getStringLiteralCompletionEntriesFromModuleNames(node: StringLiteral, compilerOptions: CompilerOptions, host: LanguageServiceHost, typeChecker: TypeChecker): CompletionInfo { + const literalValue = normalizeSlashes(node.text); + + const scriptPath = node.getSourceFile().path; + const scriptDirectory = getDirectoryPath(scriptPath); + + const span = getDirectoryFragmentTextSpan((node).text, node.getStart() + 1); + let entries: CompletionEntry[]; + if (isPathRelativeToScript(literalValue) || isRootedDiskPath(literalValue)) { + const extensions = getSupportedExtensions(compilerOptions); + if (compilerOptions.rootDirs) { + entries = getCompletionEntriesForDirectoryFragmentWithRootDirs( + compilerOptions.rootDirs, literalValue, scriptDirectory, extensions, /*includeExtensions*/false, span, compilerOptions, host, scriptPath); + } + else { + entries = getCompletionEntriesForDirectoryFragment( + literalValue, scriptDirectory, extensions, /*includeExtensions*/false, span, host, scriptPath); + } + } + else { + // Check for node modules + entries = getCompletionEntriesForNonRelativeModules(literalValue, scriptDirectory, span, compilerOptions, host, typeChecker); + } + return { + isGlobalCompletion: false, + isMemberCompletion: false, + isNewIdentifierLocation: true, + entries + }; + } + + /** + * Takes a script path and returns paths for all potential folders that could be merged with its + * containing folder via the "rootDirs" compiler option + */ + function getBaseDirectoriesFromRootDirs(rootDirs: string[], basePath: string, scriptPath: string, ignoreCase: boolean): string[] { + // Make all paths absolute/normalized if they are not already + rootDirs = map(rootDirs, rootDirectory => normalizePath(isRootedDiskPath(rootDirectory) ? rootDirectory : combinePaths(basePath, rootDirectory))); + + // Determine the path to the directory containing the script relative to the root directory it is contained within + let relativeDirectory: string; + for (const rootDirectory of rootDirs) { + if (containsPath(rootDirectory, scriptPath, basePath, ignoreCase)) { + relativeDirectory = scriptPath.substr(rootDirectory.length); + break; + } + } + + // Now find a path for each potential directory that is to be merged with the one containing the script + return deduplicate(map(rootDirs, rootDirectory => combinePaths(rootDirectory, relativeDirectory))); + } + + function getCompletionEntriesForDirectoryFragmentWithRootDirs(rootDirs: string[], fragment: string, scriptPath: string, extensions: string[], includeExtensions: boolean, span: TextSpan, compilerOptions: CompilerOptions, host: LanguageServiceHost, exclude?: string): CompletionEntry[] { + const basePath = compilerOptions.project || host.getCurrentDirectory(); + const ignoreCase = !(host.useCaseSensitiveFileNames && host.useCaseSensitiveFileNames()); + const baseDirectories = getBaseDirectoriesFromRootDirs(rootDirs, basePath, scriptPath, ignoreCase); + + const result: CompletionEntry[] = []; + + for (const baseDirectory of baseDirectories) { + getCompletionEntriesForDirectoryFragment(fragment, baseDirectory, extensions, includeExtensions, span, host, exclude, result); + } + + return result; + } + + /** + * Given a path ending at a directory, gets the completions for the path, and filters for those entries containing the basename. + */ + function getCompletionEntriesForDirectoryFragment(fragment: string, scriptPath: string, extensions: string[], includeExtensions: boolean, span: TextSpan, host: LanguageServiceHost, exclude?: string, result: CompletionEntry[] = []): CompletionEntry[] { + if (fragment === undefined) { + fragment = ""; + } + + fragment = normalizeSlashes(fragment); + + /** + * Remove the basename from the path. Note that we don't use the basename to filter completions; + * the client is responsible for refining completions. + */ + fragment = getDirectoryPath(fragment); + + if (fragment === "") { + fragment = "." + directorySeparator; + } + + fragment = ensureTrailingDirectorySeparator(fragment); + + const absolutePath = normalizeAndPreserveTrailingSlash(isRootedDiskPath(fragment) ? fragment : combinePaths(scriptPath, fragment)); + const baseDirectory = getDirectoryPath(absolutePath); + const ignoreCase = !(host.useCaseSensitiveFileNames && host.useCaseSensitiveFileNames()); + + if (tryDirectoryExists(host, baseDirectory)) { + // Enumerate the available files if possible + const files = tryReadDirectory(host, baseDirectory, extensions, /*exclude*/undefined, /*include*/["./*"]); + + if (files) { + /** + * Multiple file entries might map to the same truncated name once we remove extensions + * (happens iff includeExtensions === false)so we use a set-like data structure. Eg: + * + * both foo.ts and foo.tsx become foo + */ + const foundFiles = createMap(); + for (let filePath of files) { + filePath = normalizePath(filePath); + if (exclude && comparePaths(filePath, exclude, scriptPath, ignoreCase) === Comparison.EqualTo) { + continue; + } + + const foundFileName = includeExtensions ? getBaseFileName(filePath) : removeFileExtension(getBaseFileName(filePath)); + + if (!foundFiles.get(foundFileName)) { + foundFiles.set(foundFileName, true); + } + } + + forEachKey(foundFiles, foundFile => { + result.push(createCompletionEntryForModule(foundFile, ScriptElementKind.scriptElement, span)); + }); + } + + // If possible, get folder completion as well + const directories = tryGetDirectories(host, baseDirectory); + + if (directories) { + for (const directory of directories) { + const directoryName = getBaseFileName(normalizePath(directory)); + + result.push(createCompletionEntryForModule(directoryName, ScriptElementKind.directory, span)); + } + } + } + + return result; + } + + /** + * Check all of the declared modules and those in node modules. Possible sources of modules: + * Modules that are found by the type checker + * Modules found relative to "baseUrl" compliler options (including patterns from "paths" compiler option) + * Modules from node_modules (i.e. those listed in package.json) + * This includes all files that are found in node_modules/moduleName/ with acceptable file extensions + */ + function getCompletionEntriesForNonRelativeModules(fragment: string, scriptPath: string, span: TextSpan, compilerOptions: CompilerOptions, host: LanguageServiceHost, typeChecker: TypeChecker): CompletionEntry[] { + const { baseUrl, paths } = compilerOptions; + + let result: CompletionEntry[]; + + if (baseUrl) { + const fileExtensions = getSupportedExtensions(compilerOptions); + const projectDir = compilerOptions.project || host.getCurrentDirectory(); + const absolute = isRootedDiskPath(baseUrl) ? baseUrl : combinePaths(projectDir, baseUrl); + result = getCompletionEntriesForDirectoryFragment(fragment, normalizePath(absolute), fileExtensions, /*includeExtensions*/false, span, host); + + if (paths) { + for (const path in paths) { + if (paths.hasOwnProperty(path)) { + if (path === "*") { + if (paths[path]) { + for (const pattern of paths[path]) { + for (const match of getModulesForPathsPattern(fragment, baseUrl, pattern, fileExtensions, host)) { + result.push(createCompletionEntryForModule(match, ScriptElementKind.externalModuleName, span)); + } + } + } + } + else if (startsWith(path, fragment)) { + const entry = paths[path] && paths[path].length === 1 && paths[path][0]; + if (entry) { + result.push(createCompletionEntryForModule(path, ScriptElementKind.externalModuleName, span)); + } + } + } + } + } + } + else { + result = []; + } + + getCompletionEntriesFromTypings(host, compilerOptions, scriptPath, span, result); + + for (const moduleName of enumeratePotentialNonRelativeModules(fragment, scriptPath, compilerOptions, typeChecker, host)) { + result.push(createCompletionEntryForModule(moduleName, ScriptElementKind.externalModuleName, span)); + } + + return result; + } + + function getModulesForPathsPattern(fragment: string, baseUrl: string, pattern: string, fileExtensions: string[], host: LanguageServiceHost): string[] { + if (host.readDirectory) { + const parsed = hasZeroOrOneAsteriskCharacter(pattern) ? tryParsePattern(pattern) : undefined; + if (parsed) { + // The prefix has two effective parts: the directory path and the base component after the filepath that is not a + // full directory component. For example: directory/path/of/prefix/base* + const normalizedPrefix = normalizeAndPreserveTrailingSlash(parsed.prefix); + const normalizedPrefixDirectory = getDirectoryPath(normalizedPrefix); + const normalizedPrefixBase = getBaseFileName(normalizedPrefix); + + const fragmentHasPath = fragment.indexOf(directorySeparator) !== -1; + + // Try and expand the prefix to include any path from the fragment so that we can limit the readDirectory call + const expandedPrefixDirectory = fragmentHasPath ? combinePaths(normalizedPrefixDirectory, normalizedPrefixBase + getDirectoryPath(fragment)) : normalizedPrefixDirectory; + + const normalizedSuffix = normalizePath(parsed.suffix); + const baseDirectory = combinePaths(baseUrl, expandedPrefixDirectory); + const completePrefix = fragmentHasPath ? baseDirectory : ensureTrailingDirectorySeparator(baseDirectory) + normalizedPrefixBase; + + // If we have a suffix, then we need to read the directory all the way down. We could create a glob + // that encodes the suffix, but we would have to escape the character "?" which readDirectory + // doesn't support. For now, this is safer but slower + const includeGlob = normalizedSuffix ? "**/*" : "./*"; + + const matches = tryReadDirectory(host, baseDirectory, fileExtensions, undefined, [includeGlob]); + if (matches) { + const result: string[] = []; + + // Trim away prefix and suffix + for (const match of matches) { + const normalizedMatch = normalizePath(match); + if (!endsWith(normalizedMatch, normalizedSuffix) || !startsWith(normalizedMatch, completePrefix)) { + continue; + } + + const start = completePrefix.length; + const length = normalizedMatch.length - start - normalizedSuffix.length; + + result.push(removeFileExtension(normalizedMatch.substr(start, length))); + } + return result; + } + } + } + + return undefined; + } + + function enumeratePotentialNonRelativeModules(fragment: string, scriptPath: string, options: CompilerOptions, typeChecker: TypeChecker, host: LanguageServiceHost): string[] { + // Check If this is a nested module + const isNestedModule = fragment.indexOf(directorySeparator) !== -1; + const moduleNameFragment = isNestedModule ? fragment.substr(0, fragment.lastIndexOf(directorySeparator)) : undefined; + + // Get modules that the type checker picked up + const ambientModules = map(typeChecker.getAmbientModules(), sym => stripQuotes(sym.name)); + let nonRelativeModules = filter(ambientModules, moduleName => startsWith(moduleName, fragment)); + + // Nested modules of the form "module-name/sub" need to be adjusted to only return the string + // after the last '/' that appears in the fragment because that's where the replacement span + // starts + if (isNestedModule) { + const moduleNameWithSeperator = ensureTrailingDirectorySeparator(moduleNameFragment); + nonRelativeModules = map(nonRelativeModules, moduleName => { + if (startsWith(fragment, moduleNameWithSeperator)) { + return moduleName.substr(moduleNameWithSeperator.length); + } + return moduleName; + }); + } + + + if (!options.moduleResolution || options.moduleResolution === ModuleResolutionKind.NodeJs) { + for (const visibleModule of enumerateNodeModulesVisibleToScript(host, scriptPath)) { + if (!isNestedModule) { + nonRelativeModules.push(visibleModule.moduleName); + } + else if (startsWith(visibleModule.moduleName, moduleNameFragment)) { + const nestedFiles = tryReadDirectory(host, visibleModule.moduleDir, supportedTypeScriptExtensions, /*exclude*/undefined, /*include*/["./*"]); + if (nestedFiles) { + for (let f of nestedFiles) { + f = normalizePath(f); + const nestedModule = removeFileExtension(getBaseFileName(f)); + nonRelativeModules.push(nestedModule); + } + } + } + } + } + + return deduplicate(nonRelativeModules); + } + + export function getTripleSlashReferenceCompletion(sourceFile: SourceFile, position: number, compilerOptions: CompilerOptions, host: LanguageServiceHost): CompletionInfo { + const token = getTokenAtPosition(sourceFile, position); + if (!token) { + return undefined; + } + const commentRanges: CommentRange[] = getLeadingCommentRanges(sourceFile.text, token.pos); + + if (!commentRanges || !commentRanges.length) { + return undefined; + } + + const range = forEach(commentRanges, commentRange => position >= commentRange.pos && position <= commentRange.end && commentRange); + + if (!range) { + return undefined; + } + + const completionInfo: CompletionInfo = { + /** + * We don't want the editor to offer any other completions, such as snippets, inside a comment. + */ + isGlobalCompletion: false, + isMemberCompletion: false, + /** + * The user may type in a path that doesn't yet exist, creating a "new identifier" + * with respect to the collection of identifiers the server is aware of. + */ + isNewIdentifierLocation: true, + + entries: [] + }; + + const text = sourceFile.text.substr(range.pos, position - range.pos); + + const match = tripleSlashDirectiveFragmentRegex.exec(text); + + if (match) { + const prefix = match[1]; + const kind = match[2]; + const toComplete = match[3]; + + const scriptPath = getDirectoryPath(sourceFile.path); + if (kind === "path") { + // Give completions for a relative path + const span: TextSpan = getDirectoryFragmentTextSpan(toComplete, range.pos + prefix.length); + completionInfo.entries = getCompletionEntriesForDirectoryFragment(toComplete, scriptPath, getSupportedExtensions(compilerOptions), /*includeExtensions*/true, span, host, sourceFile.path); + } + else { + // Give completions based on the typings available + const span: TextSpan = { start: range.pos + prefix.length, length: match[0].length - prefix.length }; + completionInfo.entries = getCompletionEntriesFromTypings(host, compilerOptions, scriptPath, span); + } + } + + return completionInfo; + } + + function getCompletionEntriesFromTypings(host: LanguageServiceHost, options: CompilerOptions, scriptPath: string, span: TextSpan, result: CompletionEntry[] = []): CompletionEntry[] { + // Check for typings specified in compiler options + if (options.types) { + for (const moduleName of options.types) { + result.push(createCompletionEntryForModule(moduleName, ScriptElementKind.externalModuleName, span)); + } + } + else if (host.getDirectories) { + let typeRoots: string[]; + try { + // Wrap in try catch because getEffectiveTypeRoots touches the filesystem + typeRoots = getEffectiveTypeRoots(options, host); + } + catch (e) {} + + if (typeRoots) { + for (const root of typeRoots) { + getCompletionEntriesFromDirectories(host, root, span, result); + } + } + } + + if (host.getDirectories) { + // Also get all @types typings installed in visible node_modules directories + for (const packageJson of findPackageJsons(scriptPath, host)) { + const typesDir = combinePaths(getDirectoryPath(packageJson), "node_modules/@types"); + getCompletionEntriesFromDirectories(host, typesDir, span, result); + } + } + + return result; + } + + function getCompletionEntriesFromDirectories(host: LanguageServiceHost, directory: string, span: TextSpan, result: Push) { + if (host.getDirectories && tryDirectoryExists(host, directory)) { + const directories = tryGetDirectories(host, directory); + if (directories) { + for (let typeDirectory of directories) { + typeDirectory = normalizePath(typeDirectory); + result.push(createCompletionEntryForModule(getBaseFileName(typeDirectory), ScriptElementKind.externalModuleName, span)); + } + } + } + } + + function findPackageJsons(currentDir: string, host: LanguageServiceHost): string[] { + const paths: string[] = []; + let currentConfigPath: string; + while (true) { + currentConfigPath = findConfigFile(currentDir, (f) => tryFileExists(host, f), "package.json"); + if (currentConfigPath) { + paths.push(currentConfigPath); + + currentDir = getDirectoryPath(currentConfigPath); + const parent = getDirectoryPath(currentDir); + if (currentDir === parent) { + break; + } + currentDir = parent; + } + else { + break; + } + } + + return paths; + } + + function enumerateNodeModulesVisibleToScript(host: LanguageServiceHost, scriptPath: string) { + const result: VisibleModuleInfo[] = []; + + if (host.readFile && host.fileExists) { + for (const packageJson of findPackageJsons(scriptPath, host)) { + const contents = tryReadingPackageJson(packageJson); + if (!contents) { + return; + } + + const nodeModulesDir = combinePaths(getDirectoryPath(packageJson), "node_modules"); + const foundModuleNames: string[] = []; + + // Provide completions for all non @types dependencies + for (const key of nodeModulesDependencyKeys) { + addPotentialPackageNames(contents[key], foundModuleNames); + } + + for (const moduleName of foundModuleNames) { + const moduleDir = combinePaths(nodeModulesDir, moduleName); + result.push({ + moduleName, + moduleDir + }); + } + } + } + + return result; + + function tryReadingPackageJson(filePath: string) { + try { + const fileText = tryReadFile(host, filePath); + return fileText ? JSON.parse(fileText) : undefined; + } + catch (e) { + return undefined; + } + } + + function addPotentialPackageNames(dependencies: any, result: string[]) { + if (dependencies) { + for (const dep in dependencies) { + if (dependencies.hasOwnProperty(dep) && !startsWith(dep, "@types/")) { + result.push(dep); + } + } + } + } + } + + function createCompletionEntryForModule(name: string, kind: string, replacementSpan: TextSpan): CompletionEntry { + return { name, kind, kindModifiers: ScriptElementKindModifier.none, sortText: name, replacementSpan }; + } + + // Replace everything after the last directory seperator that appears + function getDirectoryFragmentTextSpan(text: string, textStart: number): TextSpan { + const index = text.lastIndexOf(directorySeparator); + const offset = index !== -1 ? index + 1 : 0; + return { start: textStart + offset, length: text.length - offset }; + } + + // Returns true if the path is explicitly relative to the script (i.e. relative to . or ..) + function isPathRelativeToScript(path: string) { + if (path && path.length >= 2 && path.charCodeAt(0) === CharacterCodes.dot) { + const slashIndex = path.length >= 3 && path.charCodeAt(1) === CharacterCodes.dot ? 2 : 1; + const slashCharCode = path.charCodeAt(slashIndex); + return slashCharCode === CharacterCodes.slash || slashCharCode === CharacterCodes.backslash; + } + return false; + } + + function normalizeAndPreserveTrailingSlash(path: string) { + return hasTrailingDirectorySeparator(path) ? ensureTrailingDirectorySeparator(normalizePath(path)) : normalizePath(path); + } + + /** + * Matches a triple slash reference directive with an incomplete string literal for its path. Used + * to determine if the caret is currently within the string literal and capture the literal fragment + * for completions. + * For example, this matches + * + * /// (host: LanguageServiceHost, toApply: (...a: any[]) => T, ...args: any[]) { + try { + return toApply && toApply.apply(host, args); + } + catch (e) {} + return undefined; + } +} diff --git a/src/services/symbolDisplay.ts b/src/services/symbolDisplay.ts index a6afce1cc4f..b7b739b4244 100644 --- a/src/services/symbolDisplay.ts +++ b/src/services/symbolDisplay.ts @@ -2,7 +2,7 @@ namespace ts.SymbolDisplay { // TODO(drosen): use contextual SemanticMeaning. export function getSymbolKind(typeChecker: TypeChecker, symbol: Symbol, location: Node): string { - const flags = symbol.getFlags(); + const { flags } = symbol; if (flags & SymbolFlags.Class) return getDeclarationOfKind(symbol, SyntaxKind.ClassExpression) ? ScriptElementKind.localClassElement : ScriptElementKind.classElement; @@ -11,10 +11,10 @@ namespace ts.SymbolDisplay { if (flags & SymbolFlags.Interface) return ScriptElementKind.interfaceElement; if (flags & SymbolFlags.TypeParameter) return ScriptElementKind.typeParameterElement; - const result = getSymbolKindOfConstructorPropertyMethodAccessorFunctionOrVar(typeChecker, symbol, flags, location); + const result = getSymbolKindOfConstructorPropertyMethodAccessorFunctionOrVar(typeChecker, symbol, location); if (result === ScriptElementKind.unknown) { if (flags & SymbolFlags.TypeParameter) return ScriptElementKind.typeParameterElement; - if (flags & SymbolFlags.EnumMember) return ScriptElementKind.variableElement; + if (flags & SymbolFlags.EnumMember) return ScriptElementKind.enumMemberElement; if (flags & SymbolFlags.Alias) return ScriptElementKind.alias; if (flags & SymbolFlags.Module) return ScriptElementKind.moduleElement; } @@ -22,7 +22,7 @@ namespace ts.SymbolDisplay { return result; } - function getSymbolKindOfConstructorPropertyMethodAccessorFunctionOrVar(typeChecker: TypeChecker, symbol: Symbol, flags: SymbolFlags, location: Node) { + function getSymbolKindOfConstructorPropertyMethodAccessorFunctionOrVar(typeChecker: TypeChecker, symbol: Symbol, location: Node) { if (typeChecker.isUndefinedSymbol(symbol)) { return ScriptElementKind.variableElement; } @@ -32,6 +32,7 @@ namespace ts.SymbolDisplay { if (location.kind === SyntaxKind.ThisKeyword && isExpression(location)) { return ScriptElementKind.parameterElement; } + const { flags } = symbol; if (flags & SymbolFlags.Variable) { if (isFirstDeclarationOfSymbolParameter(symbol)) { return ScriptElementKind.parameterElement; @@ -93,7 +94,7 @@ namespace ts.SymbolDisplay { const displayParts: SymbolDisplayPart[] = []; let documentation: SymbolDisplayPart[]; const symbolFlags = symbol.flags; - let symbolKind = getSymbolKindOfConstructorPropertyMethodAccessorFunctionOrVar(typeChecker, symbol, symbolFlags, location); + let symbolKind = getSymbolKindOfConstructorPropertyMethodAccessorFunctionOrVar(typeChecker, symbol, location); let hasAddedSymbolInfo: boolean; const isThisExpression = location.kind === SyntaxKind.ThisKeyword && isExpression(location); let type: Type; @@ -319,6 +320,7 @@ namespace ts.SymbolDisplay { } } if (symbolFlags & SymbolFlags.EnumMember) { + symbolKind = ScriptElementKind.enumMemberElement; addPrefixForAnyFunctionOrVar(symbol, "enum member"); const declaration = symbol.declarations[0]; if (declaration.kind === SyntaxKind.EnumMember) { diff --git a/src/services/tsconfig.json b/src/services/tsconfig.json index ae2150d981d..93f861e80aa 100644 --- a/src/services/tsconfig.json +++ b/src/services/tsconfig.json @@ -53,6 +53,7 @@ "navigateTo.ts", "navigationBar.ts", "outliningElementsCollector.ts", + "pathCompletions.ts", "patternMatcher.ts", "preProcess.ts", "rename.ts", diff --git a/src/services/types.ts b/src/services/types.ts index 9253153c004..c7852db6d6e 100644 --- a/src/services/types.ts +++ b/src/services/types.ts @@ -706,8 +706,7 @@ namespace ts { /** enum E */ export const enumElement = "enum"; - // TODO: GH#9983 - export const enumMemberElement = "const"; + export const enumMemberElement = "enum member"; /** * Inside module and script only diff --git a/tests/baselines/reference/ambientRequireFunction.types b/tests/baselines/reference/ambientRequireFunction.types index 73a163f860b..5ae9a85188c 100644 --- a/tests/baselines/reference/ambientRequireFunction.types +++ b/tests/baselines/reference/ambientRequireFunction.types @@ -3,7 +3,7 @@ const fs = require("fs"); >fs : typeof "fs" ->require("fs") : any +>require("fs") : typeof "fs" >require : (moduleName: string) => any >"fs" : "fs" diff --git a/tests/baselines/reference/circularInferredTypeOfVariable.js b/tests/baselines/reference/circularInferredTypeOfVariable.js new file mode 100644 index 00000000000..38c5334761a --- /dev/null +++ b/tests/baselines/reference/circularInferredTypeOfVariable.js @@ -0,0 +1,44 @@ +//// [circularInferredTypeOfVariable.ts] + +// Repro from #14428 + +(async () => { + function foo(p: string[]): string[] { + return []; + } + + function bar(p: string[]): string[] { + return []; + } + + let a1: string[] | undefined = []; + + while (true) { + let a2 = foo(a1!); + a1 = await bar(a2); + } +}); + +//// [circularInferredTypeOfVariable.js] +// Repro from #14428 +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +(() => __awaiter(this, void 0, void 0, function* () { + function foo(p) { + return []; + } + function bar(p) { + return []; + } + let a1 = []; + while (true) { + let a2 = foo(a1); + a1 = yield bar(a2); + } +})); diff --git a/tests/baselines/reference/circularInferredTypeOfVariable.symbols b/tests/baselines/reference/circularInferredTypeOfVariable.symbols new file mode 100644 index 00000000000..653978f0e83 --- /dev/null +++ b/tests/baselines/reference/circularInferredTypeOfVariable.symbols @@ -0,0 +1,34 @@ +=== tests/cases/compiler/circularInferredTypeOfVariable.ts === + +// Repro from #14428 + +(async () => { + function foo(p: string[]): string[] { +>foo : Symbol(foo, Decl(circularInferredTypeOfVariable.ts, 3, 14)) +>p : Symbol(p, Decl(circularInferredTypeOfVariable.ts, 4, 17)) + + return []; + } + + function bar(p: string[]): string[] { +>bar : Symbol(bar, Decl(circularInferredTypeOfVariable.ts, 6, 5)) +>p : Symbol(p, Decl(circularInferredTypeOfVariable.ts, 8, 17)) + + return []; + } + + let a1: string[] | undefined = []; +>a1 : Symbol(a1, Decl(circularInferredTypeOfVariable.ts, 12, 7)) + + while (true) { + let a2 = foo(a1!); +>a2 : Symbol(a2, Decl(circularInferredTypeOfVariable.ts, 15, 11)) +>foo : Symbol(foo, Decl(circularInferredTypeOfVariable.ts, 3, 14)) +>a1 : Symbol(a1, Decl(circularInferredTypeOfVariable.ts, 12, 7)) + + a1 = await bar(a2); +>a1 : Symbol(a1, Decl(circularInferredTypeOfVariable.ts, 12, 7)) +>bar : Symbol(bar, Decl(circularInferredTypeOfVariable.ts, 6, 5)) +>a2 : Symbol(a2, Decl(circularInferredTypeOfVariable.ts, 15, 11)) + } +}); diff --git a/tests/baselines/reference/circularInferredTypeOfVariable.types b/tests/baselines/reference/circularInferredTypeOfVariable.types new file mode 100644 index 00000000000..df227bd9ae0 --- /dev/null +++ b/tests/baselines/reference/circularInferredTypeOfVariable.types @@ -0,0 +1,47 @@ +=== tests/cases/compiler/circularInferredTypeOfVariable.ts === + +// Repro from #14428 + +(async () => { +>(async () => { function foo(p: string[]): string[] { return []; } function bar(p: string[]): string[] { return []; } let a1: string[] | undefined = []; while (true) { let a2 = foo(a1!); a1 = await bar(a2); }}) : () => Promise +>async () => { function foo(p: string[]): string[] { return []; } function bar(p: string[]): string[] { return []; } let a1: string[] | undefined = []; while (true) { let a2 = foo(a1!); a1 = await bar(a2); }} : () => Promise + + function foo(p: string[]): string[] { +>foo : (p: string[]) => string[] +>p : string[] + + return []; +>[] : undefined[] + } + + function bar(p: string[]): string[] { +>bar : (p: string[]) => string[] +>p : string[] + + return []; +>[] : undefined[] + } + + let a1: string[] | undefined = []; +>a1 : string[] +>[] : undefined[] + + while (true) { +>true : true + + let a2 = foo(a1!); +>a2 : string[] +>foo(a1!) : string[] +>foo : (p: string[]) => string[] +>a1! : string[] +>a1 : string[] + + a1 = await bar(a2); +>a1 = await bar(a2) : string[] +>a1 : string[] +>await bar(a2) : string[] +>bar(a2) : string[] +>bar : (p: string[]) => string[] +>a2 : string[] + } +}); diff --git a/tests/baselines/reference/controlFlowIterationErrors.errors.txt b/tests/baselines/reference/controlFlowIterationErrors.errors.txt index 081a372e271..28cbf8bdc47 100644 --- a/tests/baselines/reference/controlFlowIterationErrors.errors.txt +++ b/tests/baselines/reference/controlFlowIterationErrors.errors.txt @@ -6,15 +6,9 @@ tests/cases/conformance/controlFlow/controlFlowIterationErrors.ts(34,17): error Type 'string' is not assignable to type 'number'. tests/cases/conformance/controlFlow/controlFlowIterationErrors.ts(45,17): error TS2345: Argument of type 'string | number' is not assignable to parameter of type 'number'. Type 'string' is not assignable to type 'number'. -tests/cases/conformance/controlFlow/controlFlowIterationErrors.ts(76,13): error TS7022: 'y' implicitly has type 'any' because it does not have a type annotation and is referenced directly or indirectly in its own initializer. -tests/cases/conformance/controlFlow/controlFlowIterationErrors.ts(76,26): error TS2345: Argument of type 'string | number | boolean' is not assignable to parameter of type 'string | number'. - Type 'true' is not assignable to type 'string | number'. -tests/cases/conformance/controlFlow/controlFlowIterationErrors.ts(87,13): error TS7022: 'y' implicitly has type 'any' because it does not have a type annotation and is referenced directly or indirectly in its own initializer. -tests/cases/conformance/controlFlow/controlFlowIterationErrors.ts(87,26): error TS2345: Argument of type 'string | number | boolean' is not assignable to parameter of type 'string | number'. - Type 'true' is not assignable to type 'string | number'. -==== tests/cases/conformance/controlFlow/controlFlowIterationErrors.ts (8 errors) ==== +==== tests/cases/conformance/controlFlow/controlFlowIterationErrors.ts (4 errors) ==== let cond: boolean; function len(s: string) { @@ -103,11 +97,6 @@ tests/cases/conformance/controlFlow/controlFlowIterationErrors.ts(87,26): error x = "0"; while (cond) { let y = asNumber(x); - ~ -!!! error TS7022: 'y' implicitly has type 'any' because it does not have a type annotation and is referenced directly or indirectly in its own initializer. - ~ -!!! error TS2345: Argument of type 'string | number | boolean' is not assignable to parameter of type 'string | number'. -!!! error TS2345: Type 'true' is not assignable to type 'string | number'. x = y + 1; x; } @@ -119,11 +108,6 @@ tests/cases/conformance/controlFlow/controlFlowIterationErrors.ts(87,26): error while (cond) { x; let y = asNumber(x); - ~ -!!! error TS7022: 'y' implicitly has type 'any' because it does not have a type annotation and is referenced directly or indirectly in its own initializer. - ~ -!!! error TS2345: Argument of type 'string | number | boolean' is not assignable to parameter of type 'string | number'. -!!! error TS2345: Type 'true' is not assignable to type 'string | number'. x = y + 1; x; } diff --git a/tests/baselines/reference/implicitAnyFromCircularInference.errors.txt b/tests/baselines/reference/implicitAnyFromCircularInference.errors.txt index d083fe97664..441d27952ae 100644 --- a/tests/baselines/reference/implicitAnyFromCircularInference.errors.txt +++ b/tests/baselines/reference/implicitAnyFromCircularInference.errors.txt @@ -7,11 +7,10 @@ tests/cases/compiler/implicitAnyFromCircularInference.ts(17,10): error TS7024: F tests/cases/compiler/implicitAnyFromCircularInference.ts(22,10): error TS7024: Function implicitly has return type 'any' because it does not have a return type annotation and is referenced directly or indirectly in one of its return expressions. tests/cases/compiler/implicitAnyFromCircularInference.ts(25,10): error TS7023: 'h' implicitly has return type 'any' because it does not have a return type annotation and is referenced directly or indirectly in one of its return expressions. tests/cases/compiler/implicitAnyFromCircularInference.ts(27,14): error TS7023: 'foo' implicitly has return type 'any' because it does not have a return type annotation and is referenced directly or indirectly in one of its return expressions. -tests/cases/compiler/implicitAnyFromCircularInference.ts(40,5): error TS7022: 's' implicitly has type 'any' because it does not have a type annotation and is referenced directly or indirectly in its own initializer. tests/cases/compiler/implicitAnyFromCircularInference.ts(45,9): error TS7023: 'x' implicitly has return type 'any' because it does not have a return type annotation and is referenced directly or indirectly in one of its return expressions. -==== tests/cases/compiler/implicitAnyFromCircularInference.ts (11 errors) ==== +==== tests/cases/compiler/implicitAnyFromCircularInference.ts (10 errors) ==== // Error expected var a: typeof a; ~ @@ -70,8 +69,6 @@ tests/cases/compiler/implicitAnyFromCircularInference.ts(45,9): error TS7023: 'x class C { // Error expected s = foo(this); - ~~~~~~~~~~~~~~ -!!! error TS7022: 's' implicitly has type 'any' because it does not have a type annotation and is referenced directly or indirectly in its own initializer. } class D { diff --git a/tests/baselines/reference/quickInfoDisplayPartsEnum1.baseline b/tests/baselines/reference/quickInfoDisplayPartsEnum1.baseline index 142840b9f01..6dc27a4a5b3 100644 --- a/tests/baselines/reference/quickInfoDisplayPartsEnum1.baseline +++ b/tests/baselines/reference/quickInfoDisplayPartsEnum1.baseline @@ -34,7 +34,7 @@ "position": 13 }, "quickInfo": { - "kind": "var", + "kind": "enum member", "kindModifiers": "", "textSpan": { "start": 13, @@ -95,7 +95,7 @@ "position": 21 }, "quickInfo": { - "kind": "var", + "kind": "enum member", "kindModifiers": "", "textSpan": { "start": 21, @@ -156,7 +156,7 @@ "position": 34 }, "quickInfo": { - "kind": "var", + "kind": "enum member", "kindModifiers": "", "textSpan": { "start": 34, @@ -357,7 +357,7 @@ "position": 71 }, "quickInfo": { - "kind": "var", + "kind": "enum member", "kindModifiers": "", "textSpan": { "start": 71, @@ -488,7 +488,7 @@ "position": 89 }, "quickInfo": { - "kind": "var", + "kind": "enum member", "kindModifiers": "", "textSpan": { "start": 89, @@ -619,7 +619,7 @@ "position": 107 }, "quickInfo": { - "kind": "var", + "kind": "enum member", "kindModifiers": "", "textSpan": { "start": 107, @@ -717,7 +717,7 @@ "position": 135 }, "quickInfo": { - "kind": "var", + "kind": "enum member", "kindModifiers": "", "textSpan": { "start": 135, @@ -778,7 +778,7 @@ "position": 143 }, "quickInfo": { - "kind": "var", + "kind": "enum member", "kindModifiers": "", "textSpan": { "start": 143, @@ -839,7 +839,7 @@ "position": 156 }, "quickInfo": { - "kind": "var", + "kind": "enum member", "kindModifiers": "", "textSpan": { "start": 156, @@ -1056,7 +1056,7 @@ "position": 205 }, "quickInfo": { - "kind": "var", + "kind": "enum member", "kindModifiers": "", "textSpan": { "start": 205, @@ -1195,7 +1195,7 @@ "position": 229 }, "quickInfo": { - "kind": "var", + "kind": "enum member", "kindModifiers": "", "textSpan": { "start": 229, @@ -1334,7 +1334,7 @@ "position": 253 }, "quickInfo": { - "kind": "var", + "kind": "enum member", "kindModifiers": "", "textSpan": { "start": 253, diff --git a/tests/baselines/reference/quickInfoDisplayPartsEnum2.baseline b/tests/baselines/reference/quickInfoDisplayPartsEnum2.baseline index efdff818b0f..43d0683faef 100644 --- a/tests/baselines/reference/quickInfoDisplayPartsEnum2.baseline +++ b/tests/baselines/reference/quickInfoDisplayPartsEnum2.baseline @@ -34,7 +34,7 @@ "position": 13 }, "quickInfo": { - "kind": "var", + "kind": "enum member", "kindModifiers": "", "textSpan": { "start": 13, @@ -99,7 +99,7 @@ "position": 23 }, "quickInfo": { - "kind": "var", + "kind": "enum member", "kindModifiers": "", "textSpan": { "start": 23, @@ -164,7 +164,7 @@ "position": 38 }, "quickInfo": { - "kind": "var", + "kind": "enum member", "kindModifiers": "", "textSpan": { "start": 38, @@ -369,7 +369,7 @@ "position": 77 }, "quickInfo": { - "kind": "var", + "kind": "enum member", "kindModifiers": "", "textSpan": { "start": 77, @@ -504,7 +504,7 @@ "position": 95 }, "quickInfo": { - "kind": "var", + "kind": "enum member", "kindModifiers": "", "textSpan": { "start": 95, @@ -639,7 +639,7 @@ "position": 113 }, "quickInfo": { - "kind": "var", + "kind": "enum member", "kindModifiers": "", "textSpan": { "start": 113, @@ -741,7 +741,7 @@ "position": 141 }, "quickInfo": { - "kind": "var", + "kind": "enum member", "kindModifiers": "", "textSpan": { "start": 141, @@ -806,7 +806,7 @@ "position": 151 }, "quickInfo": { - "kind": "var", + "kind": "enum member", "kindModifiers": "", "textSpan": { "start": 151, @@ -871,7 +871,7 @@ "position": 166 }, "quickInfo": { - "kind": "var", + "kind": "enum member", "kindModifiers": "", "textSpan": { "start": 166, @@ -1092,7 +1092,7 @@ "position": 217 }, "quickInfo": { - "kind": "var", + "kind": "enum member", "kindModifiers": "", "textSpan": { "start": 217, @@ -1235,7 +1235,7 @@ "position": 241 }, "quickInfo": { - "kind": "var", + "kind": "enum member", "kindModifiers": "", "textSpan": { "start": 241, @@ -1378,7 +1378,7 @@ "position": 265 }, "quickInfo": { - "kind": "var", + "kind": "enum member", "kindModifiers": "", "textSpan": { "start": 265, diff --git a/tests/baselines/reference/quickInfoDisplayPartsEnum3.baseline b/tests/baselines/reference/quickInfoDisplayPartsEnum3.baseline index 919816394e2..b9f6483f63a 100644 --- a/tests/baselines/reference/quickInfoDisplayPartsEnum3.baseline +++ b/tests/baselines/reference/quickInfoDisplayPartsEnum3.baseline @@ -34,7 +34,7 @@ "position": 13 }, "quickInfo": { - "kind": "var", + "kind": "enum member", "kindModifiers": "", "textSpan": { "start": 13, @@ -99,7 +99,7 @@ "position": 23 }, "quickInfo": { - "kind": "var", + "kind": "enum member", "kindModifiers": "", "textSpan": { "start": 23, @@ -164,7 +164,7 @@ "position": 38 }, "quickInfo": { - "kind": "var", + "kind": "enum member", "kindModifiers": "", "textSpan": { "start": 38, @@ -369,7 +369,7 @@ "position": 77 }, "quickInfo": { - "kind": "var", + "kind": "enum member", "kindModifiers": "", "textSpan": { "start": 77, @@ -504,7 +504,7 @@ "position": 98 }, "quickInfo": { - "kind": "var", + "kind": "enum member", "kindModifiers": "", "textSpan": { "start": 98, @@ -639,7 +639,7 @@ "position": 119 }, "quickInfo": { - "kind": "var", + "kind": "enum member", "kindModifiers": "", "textSpan": { "start": 119, @@ -741,7 +741,7 @@ "position": 150 }, "quickInfo": { - "kind": "var", + "kind": "enum member", "kindModifiers": "", "textSpan": { "start": 150, @@ -806,7 +806,7 @@ "position": 160 }, "quickInfo": { - "kind": "var", + "kind": "enum member", "kindModifiers": "", "textSpan": { "start": 160, @@ -871,7 +871,7 @@ "position": 175 }, "quickInfo": { - "kind": "var", + "kind": "enum member", "kindModifiers": "", "textSpan": { "start": 175, @@ -1092,7 +1092,7 @@ "position": 226 }, "quickInfo": { - "kind": "var", + "kind": "enum member", "kindModifiers": "", "textSpan": { "start": 226, @@ -1235,7 +1235,7 @@ "position": 253 }, "quickInfo": { - "kind": "var", + "kind": "enum member", "kindModifiers": "", "textSpan": { "start": 253, @@ -1378,7 +1378,7 @@ "position": 280 }, "quickInfo": { - "kind": "var", + "kind": "enum member", "kindModifiers": "", "textSpan": { "start": 280, diff --git a/tests/cases/compiler/circularInferredTypeOfVariable.ts b/tests/cases/compiler/circularInferredTypeOfVariable.ts new file mode 100644 index 00000000000..662b6bfc8d8 --- /dev/null +++ b/tests/cases/compiler/circularInferredTypeOfVariable.ts @@ -0,0 +1,20 @@ +// @target: es6 + +// Repro from #14428 + +(async () => { + function foo(p: string[]): string[] { + return []; + } + + function bar(p: string[]): string[] { + return []; + } + + let a1: string[] | undefined = []; + + while (true) { + let a2 = foo(a1!); + a1 = await bar(a2); + } +}); \ No newline at end of file diff --git a/tests/cases/fourslash/deleteClassWithEnumPresent.ts b/tests/cases/fourslash/deleteClassWithEnumPresent.ts index 8914e0020a0..aaa35272fa9 100644 --- a/tests/cases/fourslash/deleteClassWithEnumPresent.ts +++ b/tests/cases/fourslash/deleteClassWithEnumPresent.ts @@ -16,15 +16,15 @@ verify.navigationTree({ "childItems": [ { "text": "a", - "kind": "const" + "kind": "enum member" }, { "text": "b", - "kind": "const" + "kind": "enum member" }, { "text": "c", - "kind": "const" + "kind": "enum member" } ] } @@ -48,15 +48,15 @@ verify.navigationBar([ "childItems": [ { "text": "a", - "kind": "const" + "kind": "enum member" }, { "text": "b", - "kind": "const" + "kind": "enum member" }, { "text": "c", - "kind": "const" + "kind": "enum member" } ], "indent": 1 diff --git a/tests/cases/fourslash/extendArrayInterfaceMember.ts b/tests/cases/fourslash/extendArrayInterfaceMember.ts index ef3c06b2053..22aeed01f1d 100644 --- a/tests/cases/fourslash/extendArrayInterfaceMember.ts +++ b/tests/cases/fourslash/extendArrayInterfaceMember.ts @@ -10,7 +10,7 @@ verify.numberOfErrorsInCurrentFile(1); // - Supplied parameters do not match any signature of call target. // - Could not select overload for 'call' expression. -verify.quickInfoAt("y", "var y: any"); +verify.quickInfoAt("y", "var y: number"); goTo.eof(); edit.insert("interface Array { pop(def: T): T; }"); diff --git a/tests/cases/fourslash/navigationBarItemsInsideMethodsAndConstructors.ts b/tests/cases/fourslash/navigationBarItemsInsideMethodsAndConstructors.ts index 35dff3af14c..1613b1cb868 100644 --- a/tests/cases/fourslash/navigationBarItemsInsideMethodsAndConstructors.ts +++ b/tests/cases/fourslash/navigationBarItemsInsideMethodsAndConstructors.ts @@ -36,7 +36,7 @@ verify.navigationTree({ "childItems": [ { "text": "LocalEnumMemberInConstructor", - "kind": "const" + "kind": "enum member" } ] }, @@ -64,7 +64,7 @@ verify.navigationTree({ "childItems": [ { "text": "LocalEnumMemberInMethod", - "kind": "const" + "kind": "enum member" } ] }, @@ -144,7 +144,7 @@ verify.navigationBar([ "childItems": [ { "text": "LocalEnumMemberInConstructor", - "kind": "const" + "kind": "enum member" } ], "indent": 3 @@ -184,7 +184,7 @@ verify.navigationBar([ "childItems": [ { "text": "LocalEnumMemberInMethod", - "kind": "const" + "kind": "enum member" } ], "indent": 3 diff --git a/tests/cases/fourslash/navigationBarItemsItems.ts b/tests/cases/fourslash/navigationBarItemsItems.ts index 69422dd0cc2..4597ac6474b 100644 --- a/tests/cases/fourslash/navigationBarItemsItems.ts +++ b/tests/cases/fourslash/navigationBarItemsItems.ts @@ -130,15 +130,15 @@ verify.navigationTree({ "childItems": [ { "text": "value1", - "kind": "const" + "kind": "enum member" }, { "text": "value2", - "kind": "const" + "kind": "enum member" }, { "text": "value3", - "kind": "const" + "kind": "enum member" } ] } @@ -263,15 +263,15 @@ verify.navigationBar([ "childItems": [ { "text": "value1", - "kind": "const" + "kind": "enum member" }, { "text": "value2", - "kind": "const" + "kind": "enum member" }, { "text": "value3", - "kind": "const" + "kind": "enum member" } ], "indent": 2 diff --git a/tests/cases/fourslash/quickInfoDisplayPartsEnum1.ts b/tests/cases/fourslash/quickInfoDisplayPartsEnum1.ts index 628fe9c757e..14c7b3caf1c 100644 --- a/tests/cases/fourslash/quickInfoDisplayPartsEnum1.ts +++ b/tests/cases/fourslash/quickInfoDisplayPartsEnum1.ts @@ -19,4 +19,4 @@ /////*25*/eInstance1 = /*26*/constE./*27*/e2; /////*28*/eInstance1 = /*29*/constE./*30*/e3; -verify.baselineQuickInfo(); \ No newline at end of file +verify.baselineQuickInfo(); diff --git a/tests/cases/fourslash/server/navbar01.ts b/tests/cases/fourslash/server/navbar01.ts index 4a4f59bcf78..edbfca53afb 100644 --- a/tests/cases/fourslash/server/navbar01.ts +++ b/tests/cases/fourslash/server/navbar01.ts @@ -129,15 +129,15 @@ verify.navigationTree({ "childItems": [ { "text": "value1", - "kind": "const" + "kind": "enum member" }, { "text": "value2", - "kind": "const" + "kind": "enum member" }, { "text": "value3", - "kind": "const" + "kind": "enum member" } ] } @@ -262,15 +262,15 @@ verify.navigationBar([ "childItems": [ { "text": "value1", - "kind": "const" + "kind": "enum member" }, { "text": "value2", - "kind": "const" + "kind": "enum member" }, { "text": "value3", - "kind": "const" + "kind": "enum member" } ], "indent": 2 diff --git a/tslint.json b/tslint.json index 13de7acec63..3952d4fac29 100644 --- a/tslint.json +++ b/tslint.json @@ -17,7 +17,7 @@ "double", "avoid-escape" ], - "semicolon": [true, "ignore-bound-class-methods"], + "semicolon": [true, "always", "ignore-bound-class-methods"], "whitespace": [true, "check-branch", "check-decl",