From f2931a1320ac0c1a4e04c18835e0dfde94b93ef7 Mon Sep 17 00:00:00 2001 From: Ryan Cavanaugh Date: Thu, 16 Nov 2017 14:19:41 -0800 Subject: [PATCH 1/2] Port PR #20048 --- src/compiler/core.ts | 11 ++ .../unittests/tsserverProjectSystem.ts | 4 +- src/harness/unittests/typingsInstaller.ts | 114 ++++++++++++------ src/harness/virtualFileSystemWithWatch.ts | 4 +- src/server/editorServices.ts | 54 +++++++-- src/server/project.ts | 29 ++--- .../typingsInstaller/typingsInstaller.ts | 4 +- .../reference/api/tsserverlibrary.d.ts | 5 +- 8 files changed, 151 insertions(+), 74 deletions(-) diff --git a/src/compiler/core.ts b/src/compiler/core.ts index 8d838f1dcd3..e4b7de205ff 100644 --- a/src/compiler/core.ts +++ b/src/compiler/core.ts @@ -2413,6 +2413,17 @@ namespace ts { return (removeFileExtension(path) + newExtension); } + /** + * Takes a string like "jquery-min.4.2.3" and returns "jquery" + */ + export function removeMinAndVersionNumbers(fileName: string) { + // Match a "." or "-" followed by a version number or 'min' at the end of the name + const trailingMinOrVersion = /[.-]((min)|(\d+(\.\d+)*))$/; + + // The "min" or version may both be present, in either order, so try applying the above twice. + return fileName.replace(trailingMinOrVersion, "").replace(trailingMinOrVersion, ""); + } + export interface ObjectAllocator { getNodeConstructor(): new (kind: SyntaxKind, pos?: number, end?: number) => Node; getTokenConstructor(): new (kind: TKind, pos?: number, end?: number) => Token; diff --git a/src/harness/unittests/tsserverProjectSystem.ts b/src/harness/unittests/tsserverProjectSystem.ts index e6c61b0986b..d519c167899 100644 --- a/src/harness/unittests/tsserverProjectSystem.ts +++ b/src/harness/unittests/tsserverProjectSystem.ts @@ -1483,7 +1483,7 @@ namespace ts.projectSystem { it("ignores files excluded by a custom safe type list", () => { const file1 = { - path: "/a/b/f1.ts", + path: "/a/b/f1.js", content: "export let x = 5" }; const office = { @@ -1504,7 +1504,7 @@ namespace ts.projectSystem { it("ignores files excluded by the default type list", () => { const file1 = { - path: "/a/b/f1.ts", + path: "/a/b/f1.js", content: "export let x = 5" }; const minFile = { diff --git a/src/harness/unittests/typingsInstaller.ts b/src/harness/unittests/typingsInstaller.ts index fb1a7a26a7a..ae61c9f528c 100644 --- a/src/harness/unittests/typingsInstaller.ts +++ b/src/harness/unittests/typingsInstaller.ts @@ -304,35 +304,35 @@ namespace ts.projectSystem { // 1. react typings are installed for .jsx // 2. loose files names are matched against safe list for typings if // this is a JS project (only js, jsx, d.ts files are present) - const file1 = { + const lodashJs = { path: "/a/b/lodash.js", content: "" }; - const file2 = { + const file2Jsx = { path: "/a/b/file2.jsx", content: "" }; - const file3 = { + const file3dts = { path: "/a/b/file3.d.ts", content: "" }; - const react = { + const reactDts = { path: "/a/data/node_modules/@types/react/index.d.ts", content: "declare const react: { x: number }" }; - const lodash = { + const lodashDts = { path: "/a/data/node_modules/@types/lodash/index.d.ts", content: "declare const lodash: { x: number }" }; - const host = createServerHost([file1, file2, file3, customTypesMap]); + const host = createServerHost([lodashJs, file2Jsx, file3dts, customTypesMap]); const installer = new (class extends Installer { constructor() { super(host, { typesRegistry: createTypesRegistry("lodash", "react") }); } installWorker(_requestId: number, _args: string[], _cwd: string, cb: TI.RequestCompletedAction): void { const installedTypings = ["@types/lodash", "@types/react"]; - const typingFiles = [lodash, react]; + const typingFiles = [lodashDts, reactDts]; executeCommand(this, host, installedTypings, typingFiles, cb); } })(); @@ -342,35 +342,31 @@ namespace ts.projectSystem { projectService.openExternalProject({ projectFileName, options: { allowJS: true, moduleResolution: ModuleResolutionKind.NodeJs }, - rootFiles: [toExternalFile(file1.path), toExternalFile(file2.path), toExternalFile(file3.path)], - typeAcquisition: {} + rootFiles: [toExternalFile(lodashJs.path), toExternalFile(file2Jsx.path), toExternalFile(file3dts.path)], + typeAcquisition: { } }); const p = projectService.externalProjects[0]; projectService.checkNumberOfProjects({ externalProjects: 1 }); - checkProjectActualFiles(p, [file1.path, file2.path, file3.path]); + checkProjectActualFiles(p, [file2Jsx.path, file3dts.path]); installer.installAll(/*expectedCount*/ 1); checkNumberOfProjects(projectService, { externalProjects: 1 }); host.checkTimeoutQueueLengthAndRun(2); checkNumberOfProjects(projectService, { externalProjects: 1 }); - checkProjectActualFiles(p, [file1.path, file2.path, file3.path, lodash.path, react.path]); + checkProjectActualFiles(p, [file2Jsx.path, file3dts.path, lodashDts.path, reactDts.path]); }); - it("external project - no type acquisition, with js & ts files", () => { + it("external project - type acquisition with enable: false", () => { // Tests: - // 1. No typings are included for JS projects when the project contains ts files - const file1 = { + // Exclude + const jqueryJs = { path: "/a/b/jquery.js", content: "" }; - const file2 = { - path: "/a/b/file2.ts", - content: "" - }; - const host = createServerHost([file1, file2]); + const host = createServerHost([jqueryJs]); const installer = new (class extends Installer { constructor() { super(host, { typesRegistry: createTypesRegistry("jquery") }); @@ -390,18 +386,62 @@ namespace ts.projectSystem { projectService.openExternalProject({ projectFileName, options: { allowJS: true, moduleResolution: ModuleResolutionKind.NodeJs }, - rootFiles: [toExternalFile(file1.path), toExternalFile(file2.path)], + rootFiles: [toExternalFile(jqueryJs.path)], + typeAcquisition: { enable: false } + }); + + const p = projectService.externalProjects[0]; + projectService.checkNumberOfProjects({ externalProjects: 1 }); + + checkProjectActualFiles(p, [jqueryJs.path]); + + installer.checkPendingCommands(/*expectedCount*/ 0); + }); + it("external project - no type acquisition, with js & ts files", () => { + // Tests: + // 1. No typings are included for JS projects when the project contains ts files + const jqueryJs = { + path: "/a/b/jquery.js", + content: "" + }; + const file2Ts = { + path: "/a/b/file2.ts", + content: "" + }; + + const host = createServerHost([jqueryJs, file2Ts]); + const installer = new (class extends Installer { + constructor() { + super(host, { typesRegistry: createTypesRegistry("jquery") }); + } + enqueueInstallTypingsRequest(project: server.Project, typeAcquisition: TypeAcquisition, unresolvedImports: server.SortedReadonlyArray) { + super.enqueueInstallTypingsRequest(project, typeAcquisition, unresolvedImports); + } + installWorker(_requestId: number, _args: string[], _cwd: string, cb: TI.RequestCompletedAction): void { + const installedTypings: string[] = []; + const typingFiles: FileOrFolder[] = []; + executeCommand(this, host, installedTypings, typingFiles, cb); + } + })(); + + const projectFileName = "/a/app/test.csproj"; + const projectService = createProjectService(host, { typingsInstaller: installer }); + projectService.openExternalProject({ + projectFileName, + options: { allowJS: true, moduleResolution: ModuleResolutionKind.NodeJs }, + rootFiles: [toExternalFile(jqueryJs.path), toExternalFile(file2Ts.path)], typeAcquisition: {} }); const p = projectService.externalProjects[0]; projectService.checkNumberOfProjects({ externalProjects: 1 }); - checkProjectActualFiles(p, [file2.path]); + + checkProjectActualFiles(p, [jqueryJs.path, file2Ts.path]); installer.checkPendingCommands(/*expectedCount*/ 0); checkNumberOfProjects(projectService, { externalProjects: 1 }); - checkProjectActualFiles(p, [file2.path]); + checkProjectActualFiles(p, [jqueryJs.path, file2Ts.path]); }); it("external project - with type acquisition, with only js, d.ts files", () => { @@ -409,15 +449,15 @@ namespace ts.projectSystem { // 1. Safelist matching, type acquisition includes/excludes and package.json typings are all acquired // 2. Types for safelist matches are not included when they also appear in the type acquisition exclude list // 3. Multiple includes and excludes are respected in type acquisition - const file1 = { + const lodashJs = { path: "/a/b/lodash.js", content: "" }; - const file2 = { + const commanderJs = { path: "/a/b/commander.js", content: "" }; - const file3 = { + const file3dts = { path: "/a/b/file3.d.ts", content: "" }; @@ -448,7 +488,7 @@ namespace ts.projectSystem { content: "declare const moment: { x: number }" }; - const host = createServerHost([file1, file2, file3, packageJson, customTypesMap]); + const host = createServerHost([lodashJs, commanderJs, file3dts, packageJson, customTypesMap]); const installer = new (class extends Installer { constructor() { super(host, { typesRegistry: createTypesRegistry("jquery", "commander", "moment", "express") }); @@ -465,20 +505,24 @@ namespace ts.projectSystem { projectService.openExternalProject({ projectFileName, options: { allowJS: true, moduleResolution: ModuleResolutionKind.NodeJs }, - rootFiles: [toExternalFile(file1.path), toExternalFile(file2.path), toExternalFile(file3.path)], - typeAcquisition: { include: ["jquery", "moment"], exclude: ["lodash"] } + rootFiles: [toExternalFile(lodashJs.path), toExternalFile(commanderJs.path), toExternalFile(file3dts.path)], + typeAcquisition: { enable: true, include: ["jquery", "moment"], exclude: ["lodash"] } }); const p = projectService.externalProjects[0]; projectService.checkNumberOfProjects({ externalProjects: 1 }); - checkProjectActualFiles(p, [file1.path, file2.path, file3.path]); + checkProjectActualFiles(p, [file3dts.path]); installer.installAll(/*expectedCount*/ 1); checkNumberOfProjects(projectService, { externalProjects: 1 }); host.checkTimeoutQueueLengthAndRun(2); checkNumberOfProjects(projectService, { externalProjects: 1 }); - checkProjectActualFiles(p, [file1.path, file2.path, file3.path, commander.path, express.path, jquery.path, moment.path]); + // Commander: Existed as a JS file + // JQuery: Specified in 'include' + // Moment: Specified in 'include' + // lodash: Excluded (not present) + checkProjectActualFiles(p, [file3dts.path, commander.path, jquery.path, moment.path]); }); it("Throttle - delayed typings to install", () => { @@ -548,7 +592,7 @@ namespace ts.projectSystem { const p = projectService.externalProjects[0]; projectService.checkNumberOfProjects({ externalProjects: 1 }); - checkProjectActualFiles(p, [lodashJs.path, commanderJs.path, file3.path]); + checkProjectActualFiles(p, [file3.path]); installer.checkPendingCommands(/*expectedCount*/ 1); installer.executePendingCommands(); // expected all typings file to exist @@ -557,7 +601,7 @@ namespace ts.projectSystem { } host.checkTimeoutQueueLengthAndRun(2); checkNumberOfProjects(projectService, { externalProjects: 1 }); - checkProjectActualFiles(p, [lodashJs.path, commanderJs.path, file3.path, commander.path, express.path, jquery.path, moment.path, lodash.path]); + checkProjectActualFiles(p, [file3.path, commander.path, jquery.path, moment.path, lodash.path]); }); it("Throttle - delayed run install requests", () => { @@ -648,7 +692,7 @@ namespace ts.projectSystem { const p1 = projectService.externalProjects[0]; const p2 = projectService.externalProjects[1]; projectService.checkNumberOfProjects({ externalProjects: 2 }); - checkProjectActualFiles(p1, [lodashJs.path, commanderJs.path, file3.path]); + checkProjectActualFiles(p1, [file3.path]); checkProjectActualFiles(p2, [file3.path]); installer.executePendingCommands(); @@ -659,7 +703,7 @@ namespace ts.projectSystem { installer.executePendingCommands(); host.checkTimeoutQueueLengthAndRun(3); // for 2 projects and 1 refreshing inferred project - checkProjectActualFiles(p1, [lodashJs.path, commanderJs.path, file3.path, commander.path, jquery.path, lodash.path, cordova.path]); + checkProjectActualFiles(p1, [file3.path, commander.path, jquery.path, lodash.path, cordova.path]); checkProjectActualFiles(p2, [file3.path, grunt.path, gulp.path]); }); @@ -973,7 +1017,7 @@ namespace ts.projectSystem { } }; session.executeCommand(changeRequest); - host.checkTimeoutQueueLengthAndRun(2); // This enqueues the updategraph and refresh inferred projects + host.checkTimeoutQueueLengthAndRun(0); // This enqueues the updategraph and refresh inferred projects const version2 = proj.getCachedUnresolvedImportsPerFile_TestOnly().getVersion(); assert.equal(version1, version2, "set of unresolved imports should not change"); }); diff --git a/src/harness/virtualFileSystemWithWatch.ts b/src/harness/virtualFileSystemWithWatch.ts index 3a5a4b33843..158e460780c 100644 --- a/src/harness/virtualFileSystemWithWatch.ts +++ b/src/harness/virtualFileSystemWithWatch.ts @@ -137,9 +137,9 @@ namespace ts.TestFSWithWatch { } export function checkFileNames(caption: string, actualFileNames: ReadonlyArray, expectedFileNames: string[]) { - assert.equal(actualFileNames.length, expectedFileNames.length, `${caption}: incorrect actual number of files, expected ${expectedFileNames}, got ${actualFileNames}`); + assert.equal(actualFileNames.length, expectedFileNames.length, `${caption}: incorrect actual number of files, expected:\r\n${expectedFileNames.join("\r\n")}\r\ngot: ${actualFileNames.join("\r\n")}`); for (const f of expectedFileNames) { - assert.isTrue(contains(actualFileNames, f), `${caption}: expected to find ${f} in ${actualFileNames}`); + assert.equal(true, contains(actualFileNames, f), `${caption}: expected to find ${f} in ${actualFileNames}`); } } diff --git a/src/server/editorServices.ts b/src/server/editorServices.ts index 56e7410b6f1..d3815698cef 100644 --- a/src/server/editorServices.ts +++ b/src/server/editorServices.ts @@ -110,7 +110,7 @@ namespace ts.server { export interface TypesMapFile { typesMap: SafeList; - simpleMap: string[]; + simpleMap: { [libName: string]: string }; } /** @@ -378,6 +378,7 @@ namespace ts.server { private readonly hostConfiguration: HostConfiguration; private safelist: SafeList = defaultTypeSafeList; + private legacySafelist: { [key: string]: string } = {}; private changedFiles: ScriptInfo[]; private pendingProjectUpdates = createMap(); @@ -522,10 +523,12 @@ namespace ts.server { } // raw is now fixed and ready this.safelist = raw.typesMap; + this.legacySafelist = raw.simpleMap; } catch (e) { this.logger.info(`Error loading types map: ${e}`); this.safelist = defaultTypeSafeList; + this.legacySafelist = {}; } } @@ -2159,8 +2162,13 @@ namespace ts.server { applySafeList(proj: protocol.ExternalProject): NormalizedPath[] { const { rootFiles, typeAcquisition } = proj; - const types = (typeAcquisition && typeAcquisition.include) || []; + Debug.assert(!!typeAcquisition, "proj.typeAcquisition should be set by now"); + // If type acquisition has been explicitly disabled, do not exclude anything from the project + if (typeAcquisition.enable === false) { + return []; + } + const typeAcqInclude = typeAcquisition.include || (typeAcquisition.include = []); const excludeRules: string[] = []; const normalizedNames = rootFiles.map(f => normalizeSlashes(f.fileName)) as NormalizedPath[]; @@ -2175,8 +2183,10 @@ namespace ts.server { // If the file matches, collect its types packages and exclude rules if (rule.types) { for (const type of rule.types) { - if (types.indexOf(type) < 0) { - types.push(type); + // Best-effort de-duping here - doesn't need to be unduplicated but + // we don't want the list to become a 400-element array of just 'kendo' + if (typeAcqInclude.indexOf(type) < 0) { + typeAcqInclude.push(type); } } } @@ -2214,12 +2224,6 @@ namespace ts.server { } } } - - // Copy back this field into the project if needed - if (types.length > 0) { - proj.typeAcquisition = proj.typeAcquisition || {}; - proj.typeAcquisition.include = types; - } } const excludeRegexes = excludeRules.map(e => new RegExp(e, "i")); @@ -2229,7 +2233,29 @@ namespace ts.server { excludedFiles.push(normalizedNames[i]); } else { - filesToKeep.push(proj.rootFiles[i]); + let exclude = false; + if (typeAcquisition.enable || typeAcquisition.enableAutoDiscovery) { + const baseName = getBaseFileName(normalizedNames[i].toLowerCase()); + if (fileExtensionIs(baseName, "js")) { + const inferredTypingName = removeFileExtension(baseName); + const cleanedTypingName = removeMinAndVersionNumbers(inferredTypingName); + if (this.legacySafelist[cleanedTypingName]) { + this.logger.info(`Excluded '${normalizedNames[i]}' because it matched ${cleanedTypingName} from the legacy safelist`); + excludedFiles.push(normalizedNames[i]); + // *exclude* it from the project... + exclude = true; + // ... but *include* it in the list of types to acquire + const typeName = this.legacySafelist[cleanedTypingName]; + // Same best-effort dedupe as above + if (typeAcqInclude.indexOf(typeName) < 0) { + typeAcqInclude.push(typeName); + } + } + } + } + if (!exclude) { + filesToKeep.push(proj.rootFiles[i]); + } } } proj.rootFiles = filesToKeep; @@ -2243,6 +2269,12 @@ namespace ts.server { const typeAcquisition = convertEnableAutoDiscoveryToEnable(proj.typingOptions); proj.typeAcquisition = typeAcquisition; } + proj.typeAcquisition = proj.typeAcquisition || {}; + proj.typeAcquisition.include = proj.typeAcquisition.include || []; + proj.typeAcquisition.exclude = proj.typeAcquisition.exclude || []; + if (proj.typeAcquisition.enable === undefined) { + proj.typeAcquisition.enable = hasNoTypeScriptSource(proj.rootFiles.map(f => f.fileName)); + } const excludedFiles = this.applySafeList(proj); diff --git a/src/server/project.ts b/src/server/project.ts index da11602ea58..71be3274481 100644 --- a/src/server/project.ts +++ b/src/server/project.ts @@ -52,6 +52,11 @@ namespace ts.server { return counts.ts === 0 && counts.tsx === 0; } + /* @internal */ + export function hasNoTypeScriptSource(fileNames: string[]): boolean { + return !fileNames.some(fileName => (fileExtensionIs(fileName, Extension.Ts) && !fileExtensionIs(fileName, Extension.Dts)) || fileExtensionIs(fileName, Extension.Tsx)); + } + /* @internal */ export interface ProjectFilesWithTSDiagnostics extends protocol.ProjectFiles { projectErrors: ReadonlyArray; @@ -1436,26 +1441,10 @@ namespace ts.server { } setTypeAcquisition(newTypeAcquisition: TypeAcquisition): void { - if (!newTypeAcquisition) { - // set default typings options - newTypeAcquisition = { - enable: allRootFilesAreJsOrDts(this), - include: [], - exclude: [] - }; - } - else { - if (newTypeAcquisition.enable === undefined) { - // if autoDiscovery was not specified by the caller - set it based on the content of the project - newTypeAcquisition.enable = allRootFilesAreJsOrDts(this); - } - if (!newTypeAcquisition.include) { - newTypeAcquisition.include = []; - } - if (!newTypeAcquisition.exclude) { - newTypeAcquisition.exclude = []; - } - } + Debug.assert(!!newTypeAcquisition, "newTypeAcquisition may not be null/undefined"); + Debug.assert(!!newTypeAcquisition.include, "newTypeAcquisition.include may not be null/undefined"); + Debug.assert(!!newTypeAcquisition.exclude, "newTypeAcquisition.exclude may not be null/undefined"); + Debug.assert(typeof newTypeAcquisition.enable === "boolean", "newTypeAcquisition.enable may not be null/undefined"); this.typeAcquisition = newTypeAcquisition; } } diff --git a/src/server/typingsInstaller/typingsInstaller.ts b/src/server/typingsInstaller/typingsInstaller.ts index 26e7781b440..86e66e0fafb 100644 --- a/src/server/typingsInstaller/typingsInstaller.ts +++ b/src/server/typingsInstaller/typingsInstaller.ts @@ -123,9 +123,6 @@ namespace ts.server.typingsInstaller { this.log.writeLine(`Finished typings discovery: ${JSON.stringify(discoverTypingsResult)}`); } - // respond with whatever cached typings we have now - this.sendResponse(this.createSetTypings(req, discoverTypingsResult.cachedTypingPaths)); - // start watching files this.watchFiles(req.projectName, discoverTypingsResult.filesToWatch); @@ -134,6 +131,7 @@ namespace ts.server.typingsInstaller { this.installTypings(req, req.cachePath || this.globalCachePath, discoverTypingsResult.cachedTypingPaths, discoverTypingsResult.newTypingNames); } else { + this.sendResponse(this.createSetTypings(req, discoverTypingsResult.cachedTypingPaths)); if (this.log.isEnabled()) { this.log.writeLine(`No new typings were requested as a result of typings discovery`); } diff --git a/tests/baselines/reference/api/tsserverlibrary.d.ts b/tests/baselines/reference/api/tsserverlibrary.d.ts index 077dc0e92f1..c1ef8566927 100644 --- a/tests/baselines/reference/api/tsserverlibrary.d.ts +++ b/tests/baselines/reference/api/tsserverlibrary.d.ts @@ -7424,7 +7424,9 @@ declare namespace ts.server { } interface TypesMapFile { typesMap: SafeList; - simpleMap: string[]; + simpleMap: { + [libName: string]: string; + }; } function convertFormatOptions(protocolOptions: protocol.FormatCodeSettings): FormatCodeSettings; function convertCompilerOptions(protocolOptions: protocol.ExternalProjectCompilerOptions): CompilerOptions & protocol.CompileOnSaveMixin; @@ -7505,6 +7507,7 @@ declare namespace ts.server { private readonly throttledOperations; private readonly hostConfiguration; private safelist; + private legacySafelist; private changedFiles; private pendingProjectUpdates; private pendingInferredProjectUpdate; From b9a548cd13a277d9e7a2b1d2e6eb4472e501230f Mon Sep 17 00:00:00 2001 From: Ryan Cavanaugh Date: Thu, 16 Nov 2017 14:26:10 -0800 Subject: [PATCH 2/2] Squash port of PR #19542 --- src/compiler/core.ts | 14 ++++---- .../unittests/tsserverProjectSystem.ts | 36 +++++++++++++++++++ src/harness/unittests/typingsInstaller.ts | 10 +++--- src/server/editorServices.ts | 13 ++++--- src/services/jsTyping.ts | 2 +- .../reference/api/tsserverlibrary.d.ts | 2 +- 6 files changed, 59 insertions(+), 18 deletions(-) diff --git a/src/compiler/core.ts b/src/compiler/core.ts index e4b7de205ff..c0eb7e257a6 100644 --- a/src/compiler/core.ts +++ b/src/compiler/core.ts @@ -74,13 +74,13 @@ namespace ts { } // The global Map object. This may not be available, so we must test for it. - declare const Map: { new(): Map } | undefined; + declare const Map: { new (): Map } | undefined; // Internet Explorer's Map doesn't support iteration, so don't use it. // tslint:disable-next-line:no-in-operator const MapCtr = typeof Map !== "undefined" && "entries" in Map.prototype ? Map : shimMap(); // Keep the class inside a function so it doesn't get compiled if it's not used. - function shimMap(): { new(): Map } { + function shimMap(): { new (): Map } { class MapIterator { private data: MapLike; @@ -103,7 +103,7 @@ namespace ts { } } - return class implements Map { + return class implements Map { private data = createDictionaryObject(); public size = 0; @@ -166,8 +166,8 @@ namespace ts { } export const enum Comparison { - LessThan = -1, - EqualTo = 0, + LessThan = -1, + EqualTo = 0, GreaterThan = 1 } @@ -2626,7 +2626,7 @@ namespace ts { return findBestPatternMatch(patterns, _ => _, candidate); } - export function patternText({prefix, suffix}: Pattern): string { + export function patternText({ prefix, suffix }: Pattern): string { return `${prefix}*${suffix}`; } @@ -2656,7 +2656,7 @@ namespace ts { return matchedValue; } - function isPatternMatch({prefix, suffix}: Pattern, candidate: string) { + function isPatternMatch({ prefix, suffix }: Pattern, candidate: string) { return candidate.length >= prefix.length + suffix.length && startsWith(candidate, prefix) && endsWith(candidate, suffix); diff --git a/src/harness/unittests/tsserverProjectSystem.ts b/src/harness/unittests/tsserverProjectSystem.ts index d519c167899..1b2aa481b26 100644 --- a/src/harness/unittests/tsserverProjectSystem.ts +++ b/src/harness/unittests/tsserverProjectSystem.ts @@ -1540,6 +1540,42 @@ namespace ts.projectSystem { } }); + it("removes version numbers correctly", () => { + const testData: [string, string][] = [ + ["jquery-max", "jquery-max"], + ["jquery.min", "jquery"], + ["jquery-min.4.2.3", "jquery"], + ["jquery.min.4.2.1", "jquery"], + ["minimum", "minimum"], + ["min", "min"], + ["min.3.2", "min"], + ["jquery", "jquery"] + ]; + for (const t of testData) { + assert.equal(removeMinAndVersionNumbers(t[0]), t[1], t[0]); + } + }); + + it("ignores files excluded by a legacy safe type list", () => { + const file1 = { + path: "/a/b/bliss.js", + content: "let x = 5" + }; + const file2 = { + path: "/a/b/foo.js", + content: "" + }; + const host = createServerHost([file1, file2, customTypesMap]); + const projectService = createProjectService(host); + try { + projectService.openExternalProject({ projectFileName: "project", options: {}, rootFiles: toExternalFiles([file1.path, file2.path]), typeAcquisition: { enable: true } }); + const proj = projectService.externalProjects[0]; + assert.deepEqual(proj.getFileNames(), [file2.path]); + } finally { + projectService.resetSafeList(); + } + }); + it("open file become a part of configured project if it is referenced from root file", () => { const file1 = { path: "/a/b/f1.ts", diff --git a/src/harness/unittests/typingsInstaller.ts b/src/harness/unittests/typingsInstaller.ts index ae61c9f528c..dd5cfe904bf 100644 --- a/src/harness/unittests/typingsInstaller.ts +++ b/src/harness/unittests/typingsInstaller.ts @@ -521,8 +521,9 @@ namespace ts.projectSystem { // Commander: Existed as a JS file // JQuery: Specified in 'include' // Moment: Specified in 'include' + // Express: Specified in package.json // lodash: Excluded (not present) - checkProjectActualFiles(p, [file3dts.path, commander.path, jquery.path, moment.path]); + checkProjectActualFiles(p, [file3dts.path, commander.path, express.path, jquery.path, moment.path]); }); it("Throttle - delayed typings to install", () => { @@ -601,7 +602,7 @@ namespace ts.projectSystem { } host.checkTimeoutQueueLengthAndRun(2); checkNumberOfProjects(projectService, { externalProjects: 1 }); - checkProjectActualFiles(p, [file3.path, commander.path, jquery.path, moment.path, lodash.path]); + checkProjectActualFiles(p, [file3.path, commander.path, express.path, jquery.path, moment.path, lodash.path]); }); it("Throttle - delayed run install requests", () => { @@ -1101,11 +1102,12 @@ namespace ts.projectSystem { const host = createServerHost([app, jquery, chroma]); const logger = trackingLogger(); const result = JsTyping.discoverTypings(host, logger.log, [app.path, jquery.path, chroma.path], getDirectoryPath(app.path), safeList, emptyMap, { enable: true }, emptyArray); - assert.deepEqual(logger.finish(), [ + const finish = logger.finish(); + assert.deepEqual(finish, [ 'Inferred typings from file names: ["jquery","chroma-js"]', "Inferred typings from unresolved imports: []", 'Result: {"cachedTypingPaths":[],"newTypingNames":["jquery","chroma-js"],"filesToWatch":["/a/b/bower_components","/a/b/node_modules"]}', - ]); + ], finish.join("\r\n")); assert.deepEqual(result.newTypingNames, ["jquery", "chroma-js"]); }); diff --git a/src/server/editorServices.ts b/src/server/editorServices.ts index d3815698cef..3c2a3220350 100644 --- a/src/server/editorServices.ts +++ b/src/server/editorServices.ts @@ -431,9 +431,12 @@ namespace ts.server { this.toCanonicalFileName = createGetCanonicalFileName(this.host.useCaseSensitiveFileNames); this.throttledOperations = new ThrottledOperations(this.host, this.logger); - if (opts.typesMapLocation) { + if (this.typesMapLocation) { this.loadTypesMap(); } + else { + this.logger.info("No types map provided; using the default"); + } this.typingsInstaller.attach(this); @@ -1399,7 +1402,7 @@ namespace ts.server { return false; } - private createExternalProject(projectFileName: string, files: protocol.ExternalFile[], options: protocol.ExternalProjectCompilerOptions, typeAcquisition: TypeAcquisition) { + private createExternalProject(projectFileName: string, files: protocol.ExternalFile[], options: protocol.ExternalProjectCompilerOptions, typeAcquisition: TypeAcquisition, excludedFiles: NormalizedPath[]) { const compilerOptions = convertCompilerOptions(options); const project = new ExternalProject( projectFileName, @@ -1408,6 +1411,7 @@ namespace ts.server { compilerOptions, /*languageServiceEnabled*/ !this.exceededTotalSizeLimitForNonTsFiles(projectFileName, compilerOptions, files, externalFilePropertyReader), options.compileOnSave === undefined ? true : options.compileOnSave); + project.excludedFiles = excludedFiles; this.addFilesToNonInferredProjectAndUpdateGraph(project, files, externalFilePropertyReader, typeAcquisition); this.externalProjects.push(project); @@ -2178,7 +2182,7 @@ namespace ts.server { const rule = this.safelist[name]; for (const root of normalizedNames) { if (rule.match.test(root)) { - this.logger.info(`Excluding files based on rule ${name}`); + this.logger.info(`Excluding files based on rule ${name} matching file '${root}'`); // If the file matches, collect its types packages and exclude rules if (rule.types) { @@ -2369,8 +2373,7 @@ namespace ts.server { else { // no config files - remove the item from the collection this.externalProjectToConfiguredProjectMap.delete(proj.projectFileName); - const newProj = this.createExternalProject(proj.projectFileName, rootFiles, proj.options, proj.typeAcquisition); - newProj.excludedFiles = excludedFiles; + this.createExternalProject(proj.projectFileName, rootFiles, proj.options, proj.typeAcquisition, excludedFiles); } if (!suppressRefreshOfInferredProjects) { this.ensureProjectStructuresUptoDate(/*refreshInferredProjects*/ true); diff --git a/src/services/jsTyping.ts b/src/services/jsTyping.ts index 572858dd2fd..e31201b951f 100644 --- a/src/services/jsTyping.ts +++ b/src/services/jsTyping.ts @@ -180,7 +180,7 @@ namespace ts.JsTyping { if (!hasJavaScriptFileExtension(j)) return undefined; const inferredTypingName = removeFileExtension(getBaseFileName(j.toLowerCase())); - const cleanedTypingName = inferredTypingName.replace(/((?:\.|-)min(?=\.|$))|((?:-|\.)\d+)/g, ""); + const cleanedTypingName = removeMinAndVersionNumbers(inferredTypingName); return safeList.get(cleanedTypingName); }); if (fromFileNames.length) { diff --git a/tests/baselines/reference/api/tsserverlibrary.d.ts b/tests/baselines/reference/api/tsserverlibrary.d.ts index c1ef8566927..55b6a07bf86 100644 --- a/tests/baselines/reference/api/tsserverlibrary.d.ts +++ b/tests/baselines/reference/api/tsserverlibrary.d.ts @@ -7616,7 +7616,7 @@ declare namespace ts.server { private findExternalProjectByProjectName(projectFileName); private convertConfigFileContentToProjectOptions(configFilename, cachedDirectoryStructureHost); private exceededTotalSizeLimitForNonTsFiles(name, options, fileNames, propertyReader); - private createExternalProject(projectFileName, files, options, typeAcquisition); + private createExternalProject(projectFileName, files, options, typeAcquisition, excludedFiles); private sendProjectTelemetry(projectKey, project, projectOptions?); private addFilesToNonInferredProjectAndUpdateGraph(project, files, propertyReader, typeAcquisition); private createConfiguredProject(configFileName);