Skip to content

Port #20048 to release-2.6 #20079

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Nov 17, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 18 additions & 7 deletions src/compiler/core.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<T>(): Map<T> } | undefined;
declare const Map: { new <T>(): Map<T> } | 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<T>(): Map<T> } {
function shimMap(): { new <T>(): Map<T> } {

class MapIterator<T, U extends (string | T | [string, T])> {
private data: MapLike<T>;
Expand All @@ -103,7 +103,7 @@ namespace ts {
}
}

return class<T> implements Map<T> {
return class <T> implements Map<T> {
private data = createDictionaryObject<T>();
public size = 0;

Expand Down Expand Up @@ -166,8 +166,8 @@ namespace ts {
}

export const enum Comparison {
LessThan = -1,
EqualTo = 0,
LessThan = -1,
EqualTo = 0,
GreaterThan = 1
}

Expand Down Expand Up @@ -2413,6 +2413,17 @@ namespace ts {
return <T>(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 <TKind extends SyntaxKind>(kind: TKind, pos?: number, end?: number) => Token<TKind>;
Expand Down Expand Up @@ -2615,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}`;
}

Expand Down Expand Up @@ -2645,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);
Expand Down
40 changes: 38 additions & 2 deletions src/harness/unittests/tsserverProjectSystem.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 = {
Expand All @@ -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 = {
Expand Down Expand Up @@ -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",
Expand Down
110 changes: 78 additions & 32 deletions src/harness/unittests/typingsInstaller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
})();
Expand All @@ -342,35 +342,74 @@ 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 - type acquisition with enable: false", () => {
// Tests:
// Exclude
const jqueryJs = {
path: "/a/b/jquery.js",
content: ""
};

const host = createServerHost([jqueryJs]);
const installer = new (class extends Installer {
constructor() {
super(host, { typesRegistry: createTypesRegistry("jquery") });
}
enqueueInstallTypingsRequest(project: server.Project, typeAcquisition: TypeAcquisition, unresolvedImports: server.SortedReadonlyArray<string>) {
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)],
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 file1 = {
const jqueryJs = {
path: "/a/b/jquery.js",
content: ""
};
const file2 = {
const file2Ts = {
path: "/a/b/file2.ts",
content: ""
};

const host = createServerHost([file1, file2]);
const host = createServerHost([jqueryJs, file2Ts]);
const installer = new (class extends Installer {
constructor() {
super(host, { typesRegistry: createTypesRegistry("jquery") });
Expand All @@ -390,34 +429,35 @@ namespace ts.projectSystem {
projectService.openExternalProject({
projectFileName,
options: { allowJS: true, moduleResolution: ModuleResolutionKind.NodeJs },
rootFiles: [toExternalFile(file1.path), toExternalFile(file2.path)],
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", () => {
// Tests:
// 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: ""
};
Expand Down Expand Up @@ -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") });
Expand All @@ -465,20 +505,25 @@ 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'
// Express: Specified in package.json
// lodash: Excluded (not present)
checkProjectActualFiles(p, [file3dts.path, commander.path, express.path, jquery.path, moment.path]);
});

it("Throttle - delayed typings to install", () => {
Expand Down Expand Up @@ -548,7 +593,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
Expand All @@ -557,7 +602,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, express.path, jquery.path, moment.path, lodash.path]);
});

it("Throttle - delayed run install requests", () => {
Expand Down Expand Up @@ -648,7 +693,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();
Expand All @@ -659,7 +704,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]);
});

Expand Down Expand Up @@ -973,7 +1018,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");
});
Expand Down Expand Up @@ -1057,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(<Path>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"]);
});

Expand Down
Loading