mirror of
https://github.com/microsoft/TypeScript.git
synced 2025-11-18 17:21:48 +00:00
Merge pull request #20083 from RyanCavanaugh/port20048_2.5
Port20048 2.5
This commit is contained in:
+11
-1
@@ -463,10 +463,11 @@ gulp.task(serverFile, /*help*/ false, [servicesFile, typingsInstallerJs, cancell
|
||||
.pipe(gulp.dest("src/server"));
|
||||
});
|
||||
|
||||
const typesMapJson = path.join(builtLocalDirectory, "typesMap.json");
|
||||
const tsserverLibraryFile = path.join(builtLocalDirectory, "tsserverlibrary.js");
|
||||
const tsserverLibraryDefinitionFile = path.join(builtLocalDirectory, "tsserverlibrary.d.ts");
|
||||
|
||||
gulp.task(tsserverLibraryFile, /*help*/ false, [servicesFile], (done) => {
|
||||
gulp.task(tsserverLibraryFile, /*help*/ false, [servicesFile, typesMapJson], (done) => {
|
||||
const serverLibraryProject = tsc.createProject("src/server/tsconfig.library.json", getCompilerSettings({}, /*useBuiltCompiler*/ true));
|
||||
const {js, dts}: { js: NodeJS.ReadableStream, dts: NodeJS.ReadableStream } = serverLibraryProject.src()
|
||||
.pipe(sourcemaps.init())
|
||||
@@ -485,6 +486,15 @@ gulp.task(tsserverLibraryFile, /*help*/ false, [servicesFile], (done) => {
|
||||
]);
|
||||
});
|
||||
|
||||
gulp.task(typesMapJson, /*help*/ false, [], () => {
|
||||
return gulp.src("src/server/typesMap.json")
|
||||
.pipe(insert.transform((contents, file) => {
|
||||
JSON.parse(contents);
|
||||
return contents;
|
||||
}))
|
||||
.pipe(gulp.dest(builtLocalDirectory));
|
||||
});
|
||||
|
||||
gulp.task("lssl", "Builds language service server library", [tsserverLibraryFile]);
|
||||
gulp.task("local", "Builds the full compiler and services", [builtLocalCompiler, servicesFile, serverFile, builtGeneratedDiagnosticMessagesJSON, tsserverLibraryFile]);
|
||||
gulp.task("tsc", "Builds only the compiler", [builtLocalCompiler]);
|
||||
|
||||
+14
-1
@@ -88,6 +88,8 @@ var watchGuardSources = filesFromConfig(path.join(serverDirectory, "watchGuard/t
|
||||
var serverSources = filesFromConfig(path.join(serverDirectory, "tsconfig.json"))
|
||||
var languageServiceLibrarySources = filesFromConfig(path.join(serverDirectory, "tsconfig.library.json"));
|
||||
|
||||
var typesMapOutputPath = path.join(builtLocalDirectory, 'typesMap.json');
|
||||
|
||||
var harnessCoreSources = [
|
||||
"harness.ts",
|
||||
"virtualFileSystem.ts",
|
||||
@@ -423,6 +425,7 @@ var buildProtocolTs = path.join(scriptsDirectory, "buildProtocol.ts");
|
||||
var buildProtocolJs = path.join(scriptsDirectory, "buildProtocol.js");
|
||||
var buildProtocolDts = path.join(builtLocalDirectory, "protocol.d.ts");
|
||||
var typescriptServicesDts = path.join(builtLocalDirectory, "typescriptServices.d.ts");
|
||||
var typesMapJson = path.join(builtLocalDirectory, "typesMap.json");
|
||||
|
||||
file(buildProtocolTs);
|
||||
|
||||
@@ -587,6 +590,16 @@ var serverFile = path.join(builtLocalDirectory, "tsserver.js");
|
||||
compileFile(serverFile, serverSources, [builtLocalDirectory, copyright, cancellationTokenFile, typingsInstallerFile, watchGuardFile].concat(serverSources).concat(servicesSources), /*prefixes*/ [copyright], /*useBuiltCompiler*/ true, { types: ["node"], preserveConstEnums: true, lib: "es6" });
|
||||
var tsserverLibraryFile = path.join(builtLocalDirectory, "tsserverlibrary.js");
|
||||
var tsserverLibraryDefinitionFile = path.join(builtLocalDirectory, "tsserverlibrary.d.ts");
|
||||
file(typesMapOutputPath, function() {
|
||||
var content = fs.readFileSync(path.join(serverDirectory, 'typesMap.json'));
|
||||
// Validate that it's valid JSON
|
||||
try {
|
||||
JSON.parse(content.toString());
|
||||
} catch (e) {
|
||||
console.log("Parse error in typesMap.json: " + e);
|
||||
}
|
||||
fs.writeFileSync(typesMapOutputPath, content);
|
||||
});
|
||||
compileFile(
|
||||
tsserverLibraryFile,
|
||||
languageServiceLibrarySources,
|
||||
@@ -609,7 +622,7 @@ compileFile(
|
||||
|
||||
// Local target to build the language service server library
|
||||
desc("Builds language service server library");
|
||||
task("lssl", [tsserverLibraryFile, tsserverLibraryDefinitionFile]);
|
||||
task("lssl", [tsserverLibraryFile, tsserverLibraryDefinitionFile, typesMapOutputPath]);
|
||||
|
||||
desc("Emit the start of the build fold");
|
||||
task("build-fold-start", [], function () {
|
||||
|
||||
+18
-8
@@ -66,13 +66,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>;
|
||||
@@ -95,7 +95,7 @@ namespace ts {
|
||||
}
|
||||
}
|
||||
|
||||
return class<T> implements Map<T> {
|
||||
return class <T> implements Map<T> {
|
||||
private data = createDictionaryObject<T>();
|
||||
public size = 0;
|
||||
|
||||
@@ -158,8 +158,8 @@ namespace ts {
|
||||
}
|
||||
|
||||
export const enum Comparison {
|
||||
LessThan = -1,
|
||||
EqualTo = 0,
|
||||
LessThan = -1,
|
||||
EqualTo = 0,
|
||||
GreaterThan = 1
|
||||
}
|
||||
|
||||
@@ -2314,6 +2314,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>;
|
||||
@@ -2512,8 +2523,7 @@ namespace ts {
|
||||
return findBestPatternMatch(patterns, _ => _, candidate);
|
||||
}
|
||||
|
||||
/* @internal */
|
||||
export function patternText({prefix, suffix}: Pattern): string {
|
||||
export function patternText({ prefix, suffix }: Pattern): string {
|
||||
return `${prefix}*${suffix}`;
|
||||
}
|
||||
|
||||
@@ -2545,7 +2555,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);
|
||||
|
||||
@@ -16,8 +16,8 @@ namespace ts.server {
|
||||
directoryExists: () => false,
|
||||
getDirectories: () => [],
|
||||
createDirectory: noop,
|
||||
getExecutingFilePath(): string { return void 0; },
|
||||
getCurrentDirectory(): string { return void 0; },
|
||||
getExecutingFilePath(): string { return ""; },
|
||||
getCurrentDirectory(): string { return ""; },
|
||||
getEnvironmentVariable(): string { return ""; },
|
||||
readDirectory() { return []; },
|
||||
exit: noop,
|
||||
|
||||
@@ -28,6 +28,30 @@ namespace ts.projectSystem {
|
||||
})
|
||||
};
|
||||
|
||||
export const customTypesMap = {
|
||||
path: <Path>"/typesMap.json",
|
||||
content: `{
|
||||
"typesMap": {
|
||||
"jquery": {
|
||||
"match": "jquery(-(\\\\.?\\\\d+)+)?(\\\\.intellisense)?(\\\\.min)?\\\\.js$",
|
||||
"types": ["jquery"]
|
||||
},
|
||||
"quack": {
|
||||
"match": "/duckquack-(\\\\d+)\\\\.min\\\\.js",
|
||||
"types": ["duck-types"]
|
||||
}
|
||||
},
|
||||
"simpleMap": {
|
||||
"Bacon": "baconjs",
|
||||
"bliss": "blissfuljs",
|
||||
"commander": "commander",
|
||||
"cordova": "cordova",
|
||||
"react": "react",
|
||||
"lodash": "lodash"
|
||||
}
|
||||
}`
|
||||
};
|
||||
|
||||
export interface PostExecAction {
|
||||
readonly success: boolean;
|
||||
readonly callback: TI.RequestCompletedAction;
|
||||
@@ -204,7 +228,7 @@ namespace ts.projectSystem {
|
||||
byteLength: Utils.byteLength,
|
||||
hrtime: process.hrtime,
|
||||
logger: nullLogger,
|
||||
canUseEvents: false
|
||||
canUseEvents: false,
|
||||
};
|
||||
|
||||
return new TestSession({ ...sessionOptions, ...opts });
|
||||
@@ -230,6 +254,7 @@ namespace ts.projectSystem {
|
||||
useInferredProjectPerProjectRoot: false,
|
||||
typingsInstaller,
|
||||
eventHandler,
|
||||
typesMapLocation: customTypesMap.path,
|
||||
...opts
|
||||
});
|
||||
}
|
||||
@@ -1484,7 +1509,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 = {
|
||||
@@ -1506,7 +1531,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 = {
|
||||
@@ -1542,6 +1567,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",
|
||||
|
||||
@@ -301,35 +301,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]);
|
||||
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);
|
||||
}
|
||||
})();
|
||||
@@ -339,40 +339,36 @@ 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 });
|
||||
checkProjectActualFiles(p, [file1.path, file2.path, file3.path, lodash.path, react.path]);
|
||||
host.runQueuedTimeoutCallbacks();
|
||||
checkNumberOfProjects(projectService, { externalProjects: 1 });
|
||||
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]);
|
||||
let enqueueIsCalled = false;
|
||||
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>) {
|
||||
enqueueIsCalled = true;
|
||||
super.enqueueInstallTypingsRequest(project, typeAcquisition, unresolvedImports);
|
||||
}
|
||||
installWorker(_requestId: number, _args: string[], _cwd: string, cb: TI.RequestCompletedAction): void {
|
||||
@@ -387,18 +383,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<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), 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", () => {
|
||||
@@ -406,15 +446,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: ""
|
||||
};
|
||||
@@ -445,7 +485,7 @@ namespace ts.projectSystem {
|
||||
content: "declare const moment: { x: number }"
|
||||
};
|
||||
|
||||
const host = createServerHost([file1, file2, file3, packageJson]);
|
||||
const host = createServerHost([lodashJs, commanderJs, file3dts, packageJson, customTypesMap]);
|
||||
const installer = new (class extends Installer {
|
||||
constructor() {
|
||||
super(host, { typesRegistry: createTypesRegistry("jquery", "commander", "moment", "express") });
|
||||
@@ -462,18 +502,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 });
|
||||
checkProjectActualFiles(p, [file1.path, file2.path, file3.path, commander.path, express.path, jquery.path, moment.path]);
|
||||
host.runQueuedTimeoutCallbacks();
|
||||
checkNumberOfProjects(projectService, { externalProjects: 1 });
|
||||
// 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", () => {
|
||||
@@ -521,7 +567,7 @@ namespace ts.projectSystem {
|
||||
};
|
||||
|
||||
const typingFiles = [commander, express, jquery, moment, lodash];
|
||||
const host = createServerHost([lodashJs, commanderJs, file3, packageJson]);
|
||||
const host = createServerHost([lodashJs, commanderJs, file3, packageJson, customTypesMap]);
|
||||
const installer = new (class extends Installer {
|
||||
constructor() {
|
||||
super(host, { throttleLimit: 3, typesRegistry: createTypesRegistry("commander", "express", "jquery", "moment", "lodash") });
|
||||
@@ -543,7 +589,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
|
||||
@@ -552,7 +598,7 @@ namespace ts.projectSystem {
|
||||
}
|
||||
|
||||
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", () => {
|
||||
@@ -600,7 +646,7 @@ namespace ts.projectSystem {
|
||||
typings: typingsName("gulp")
|
||||
};
|
||||
|
||||
const host = createServerHost([lodashJs, commanderJs, file3]);
|
||||
const host = createServerHost([lodashJs, commanderJs, file3, customTypesMap]);
|
||||
const installer = new (class extends Installer {
|
||||
constructor() {
|
||||
super(host, { throttleLimit: 1, typesRegistry: createTypesRegistry("commander", "jquery", "lodash", "cordova", "gulp", "grunt") });
|
||||
@@ -643,7 +689,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();
|
||||
@@ -653,8 +699,8 @@ namespace ts.projectSystem {
|
||||
assert.equal(installer.pendingRunRequests.length, 0, "expected no throttled requests");
|
||||
|
||||
installer.executePendingCommands();
|
||||
|
||||
checkProjectActualFiles(p1, [lodashJs.path, commanderJs.path, file3.path, commander.path, jquery.path, lodash.path, cordova.path]);
|
||||
host.runQueuedTimeoutCallbacks();
|
||||
checkProjectActualFiles(p1, [file3.path, commander.path, jquery.path, lodash.path, cordova.path]);
|
||||
checkProjectActualFiles(p2, [file3.path, grunt.path, gulp.path]);
|
||||
});
|
||||
|
||||
@@ -1050,11 +1096,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"]);
|
||||
});
|
||||
|
||||
|
||||
@@ -0,0 +1,680 @@
|
||||
/// <reference path="harness.ts" />
|
||||
|
||||
namespace ts.TestFSWithWatch {
|
||||
const { content: libFileContent } = Harness.getDefaultLibraryFile(Harness.IO);
|
||||
export const libFile: FileOrFolder = {
|
||||
path: "/a/lib/lib.d.ts",
|
||||
content: libFileContent
|
||||
};
|
||||
|
||||
export const safeList = {
|
||||
path: <Path>"/safeList.json",
|
||||
content: JSON.stringify({
|
||||
commander: "commander",
|
||||
express: "express",
|
||||
jquery: "jquery",
|
||||
lodash: "lodash",
|
||||
moment: "moment",
|
||||
chroma: "chroma-js"
|
||||
})
|
||||
};
|
||||
|
||||
function getExecutingFilePathFromLibFile(): string {
|
||||
return combinePaths(getDirectoryPath(libFile.path), "tsc.js");
|
||||
}
|
||||
|
||||
interface TestServerHostCreationParameters {
|
||||
useCaseSensitiveFileNames?: boolean;
|
||||
executingFilePath?: string;
|
||||
currentDirectory?: string;
|
||||
newLine?: string;
|
||||
useWindowsStylePaths?: boolean;
|
||||
}
|
||||
|
||||
export function createWatchedSystem(fileOrFolderList: ReadonlyArray<FileOrFolder>, params?: TestServerHostCreationParameters): TestServerHost {
|
||||
if (!params) {
|
||||
params = {};
|
||||
}
|
||||
const host = new TestServerHost(/*withSafelist*/ false,
|
||||
params.useCaseSensitiveFileNames !== undefined ? params.useCaseSensitiveFileNames : false,
|
||||
params.executingFilePath || getExecutingFilePathFromLibFile(),
|
||||
params.currentDirectory || "/",
|
||||
fileOrFolderList,
|
||||
params.newLine,
|
||||
params.useWindowsStylePaths);
|
||||
return host;
|
||||
}
|
||||
|
||||
export function createServerHost(fileOrFolderList: ReadonlyArray<FileOrFolder>, params?: TestServerHostCreationParameters): TestServerHost {
|
||||
if (!params) {
|
||||
params = {};
|
||||
}
|
||||
const host = new TestServerHost(/*withSafelist*/ true,
|
||||
params.useCaseSensitiveFileNames !== undefined ? params.useCaseSensitiveFileNames : false,
|
||||
params.executingFilePath || getExecutingFilePathFromLibFile(),
|
||||
params.currentDirectory || "/",
|
||||
fileOrFolderList,
|
||||
params.newLine,
|
||||
params.useWindowsStylePaths);
|
||||
return host;
|
||||
}
|
||||
|
||||
export interface FileOrFolder {
|
||||
path: string;
|
||||
content?: string;
|
||||
fileSize?: number;
|
||||
}
|
||||
|
||||
interface FSEntry {
|
||||
path: Path;
|
||||
fullPath: string;
|
||||
}
|
||||
|
||||
interface File extends FSEntry {
|
||||
content: string;
|
||||
fileSize?: number;
|
||||
}
|
||||
|
||||
interface Folder extends FSEntry {
|
||||
entries: FSEntry[];
|
||||
}
|
||||
|
||||
function isFolder(s: FSEntry): s is Folder {
|
||||
return s && isArray((<Folder>s).entries);
|
||||
}
|
||||
|
||||
function isFile(s: FSEntry): s is File {
|
||||
return s && isString((<File>s).content);
|
||||
}
|
||||
|
||||
function invokeWatcherCallbacks<T>(callbacks: T[], invokeCallback: (cb: T) => void): void {
|
||||
if (callbacks) {
|
||||
// The array copy is made to ensure that even if one of the callback removes the callbacks,
|
||||
// we dont miss any callbacks following it
|
||||
const cbs = callbacks.slice();
|
||||
for (const cb of cbs) {
|
||||
invokeCallback(cb);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function getDiffInKeys<T>(map: Map<T>, expectedKeys: ReadonlyArray<string>) {
|
||||
if (map.size === expectedKeys.length) {
|
||||
return "";
|
||||
}
|
||||
const notInActual: string[] = [];
|
||||
const duplicates: string[] = [];
|
||||
const seen = createMap<true>();
|
||||
forEach(expectedKeys, expectedKey => {
|
||||
if (seen.has(expectedKey)) {
|
||||
duplicates.push(expectedKey);
|
||||
return;
|
||||
}
|
||||
seen.set(expectedKey, true);
|
||||
if (!map.has(expectedKey)) {
|
||||
notInActual.push(expectedKey);
|
||||
}
|
||||
});
|
||||
const inActualNotExpected: string[] = [];
|
||||
map.forEach((_value, key) => {
|
||||
if (!seen.has(key)) {
|
||||
inActualNotExpected.push(key);
|
||||
}
|
||||
seen.set(key, true);
|
||||
});
|
||||
return `\n\nNotInActual: ${notInActual}\nDuplicates: ${duplicates}\nInActualButNotInExpected: ${inActualNotExpected}`;
|
||||
}
|
||||
|
||||
export function verifyMapSize(caption: string, map: Map<any>, expectedKeys: ReadonlyArray<string>) {
|
||||
assert.equal(map.size, expectedKeys.length, `${caption}: incorrect size of map: Actual keys: ${arrayFrom(map.keys())} Expected: ${expectedKeys}${getDiffInKeys(map, expectedKeys)}`);
|
||||
}
|
||||
|
||||
function checkMapKeys(caption: string, map: Map<any>, expectedKeys: ReadonlyArray<string>) {
|
||||
verifyMapSize(caption, map, expectedKeys);
|
||||
for (const name of expectedKeys) {
|
||||
assert.isTrue(map.has(name), `${caption} is expected to contain ${name}, actual keys: ${arrayFrom(map.keys())}`);
|
||||
}
|
||||
}
|
||||
|
||||
export function checkFileNames(caption: string, actualFileNames: ReadonlyArray<string>, expectedFileNames: string[]) {
|
||||
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.equal(true, contains(actualFileNames, f), `${caption}: expected to find ${f} in ${actualFileNames}`);
|
||||
}
|
||||
}
|
||||
|
||||
export function checkWatchedFiles(host: TestServerHost, expectedFiles: string[]) {
|
||||
checkMapKeys("watchedFiles", host.watchedFiles, expectedFiles);
|
||||
}
|
||||
|
||||
export function checkWatchedDirectories(host: TestServerHost, expectedDirectories: string[], recursive = false) {
|
||||
checkMapKeys(`watchedDirectories${recursive ? " recursive" : ""}`, recursive ? host.watchedDirectoriesRecursive : host.watchedDirectories, expectedDirectories);
|
||||
}
|
||||
|
||||
export function checkOutputContains(host: TestServerHost, expected: ReadonlyArray<string>) {
|
||||
const mapExpected = arrayToSet(expected);
|
||||
const mapSeen = createMap<true>();
|
||||
for (const f of host.getOutput()) {
|
||||
assert.isUndefined(mapSeen.get(f), `Already found ${f} in ${JSON.stringify(host.getOutput())}`);
|
||||
if (mapExpected.has(f)) {
|
||||
mapExpected.delete(f);
|
||||
mapSeen.set(f, true);
|
||||
}
|
||||
}
|
||||
assert.equal(mapExpected.size, 0, `Output has missing ${JSON.stringify(flatMapIter(mapExpected.keys(), key => key))} in ${JSON.stringify(host.getOutput())}`);
|
||||
}
|
||||
|
||||
export function checkOutputDoesNotContain(host: TestServerHost, expectedToBeAbsent: string[] | ReadonlyArray<string>) {
|
||||
const mapExpectedToBeAbsent = arrayToSet(expectedToBeAbsent);
|
||||
for (const f of host.getOutput()) {
|
||||
assert.isFalse(mapExpectedToBeAbsent.has(f), `Contains ${f} in ${JSON.stringify(host.getOutput())}`);
|
||||
}
|
||||
}
|
||||
|
||||
class Callbacks {
|
||||
private map: TimeOutCallback[] = [];
|
||||
private nextId = 1;
|
||||
|
||||
getNextId() {
|
||||
return this.nextId;
|
||||
}
|
||||
|
||||
register(cb: (...args: any[]) => void, args: any[]) {
|
||||
const timeoutId = this.nextId;
|
||||
this.nextId++;
|
||||
this.map[timeoutId] = cb.bind(/*this*/ undefined, ...args);
|
||||
return timeoutId;
|
||||
}
|
||||
|
||||
unregister(id: any) {
|
||||
if (typeof id === "number") {
|
||||
delete this.map[id];
|
||||
}
|
||||
}
|
||||
|
||||
count() {
|
||||
let n = 0;
|
||||
for (const _ in this.map) {
|
||||
n++;
|
||||
}
|
||||
return n;
|
||||
}
|
||||
|
||||
invoke(invokeKey?: number) {
|
||||
if (invokeKey) {
|
||||
this.map[invokeKey]();
|
||||
delete this.map[invokeKey];
|
||||
return;
|
||||
}
|
||||
|
||||
// Note: invoking a callback may result in new callbacks been queued,
|
||||
// so do not clear the entire callback list regardless. Only remove the
|
||||
// ones we have invoked.
|
||||
for (const key in this.map) {
|
||||
this.map[key]();
|
||||
delete this.map[key];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type TimeOutCallback = () => any;
|
||||
|
||||
export interface TestFileWatcher {
|
||||
cb: FileWatcherCallback;
|
||||
fileName: string;
|
||||
}
|
||||
|
||||
export interface TestDirectoryWatcher {
|
||||
cb: DirectoryWatcherCallback;
|
||||
directoryName: string;
|
||||
}
|
||||
|
||||
export interface ReloadWatchInvokeOptions {
|
||||
invokeDirectoryWatcherInsteadOfFileChanged: boolean;
|
||||
ignoreWatchInvokedWithTriggerAsFileCreate: boolean;
|
||||
}
|
||||
|
||||
export class TestServerHost implements server.ServerHost, FormatDiagnosticsHost {
|
||||
args: string[] = [];
|
||||
|
||||
private readonly output: string[] = [];
|
||||
|
||||
private fs: Map<FSEntry> = createMap<FSEntry>();
|
||||
getCanonicalFileName: (s: string) => string;
|
||||
private toPath: (f: string) => Path;
|
||||
private timeoutCallbacks = new Callbacks();
|
||||
private immediateCallbacks = new Callbacks();
|
||||
|
||||
readonly watchedDirectories = createMultiMap<TestDirectoryWatcher>();
|
||||
readonly watchedDirectoriesRecursive = createMultiMap<TestDirectoryWatcher>();
|
||||
readonly watchedFiles = createMultiMap<TestFileWatcher>();
|
||||
private readonly executingFilePath: string;
|
||||
private readonly currentDirectory: string;
|
||||
|
||||
constructor(public withSafeList: boolean, public useCaseSensitiveFileNames: boolean, executingFilePath: string, currentDirectory: string, fileOrFolderList: ReadonlyArray<FileOrFolder>, public readonly newLine = "\n", public readonly useWindowsStylePath?: boolean) {
|
||||
this.getCanonicalFileName = createGetCanonicalFileName(useCaseSensitiveFileNames);
|
||||
this.toPath = s => toPath(s, currentDirectory, this.getCanonicalFileName);
|
||||
this.executingFilePath = this.getHostSpecificPath(executingFilePath);
|
||||
this.currentDirectory = this.getHostSpecificPath(currentDirectory);
|
||||
this.reloadFS(fileOrFolderList);
|
||||
}
|
||||
|
||||
getNewLine() {
|
||||
return this.newLine;
|
||||
}
|
||||
|
||||
toNormalizedAbsolutePath(s: string) {
|
||||
return getNormalizedAbsolutePath(s, this.currentDirectory);
|
||||
}
|
||||
|
||||
toFullPath(s: string) {
|
||||
return this.toPath(this.toNormalizedAbsolutePath(s));
|
||||
}
|
||||
|
||||
getHostSpecificPath(s: string) {
|
||||
if (this.useWindowsStylePath && s.startsWith(directorySeparator)) {
|
||||
return "c:/" + s.substring(1);
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
reloadFS(fileOrFolderList: ReadonlyArray<FileOrFolder>, options?: Partial<ReloadWatchInvokeOptions>) {
|
||||
const mapNewLeaves = createMap<true>();
|
||||
const isNewFs = this.fs.size === 0;
|
||||
fileOrFolderList = fileOrFolderList.concat(this.withSafeList ? safeList : []);
|
||||
const filesOrFoldersToLoad: ReadonlyArray<FileOrFolder> = !this.useWindowsStylePath ? fileOrFolderList :
|
||||
fileOrFolderList.map<FileOrFolder>(f => {
|
||||
const result = clone(f);
|
||||
result.path = this.getHostSpecificPath(f.path);
|
||||
return result;
|
||||
});
|
||||
for (const fileOrDirectory of filesOrFoldersToLoad) {
|
||||
const path = this.toFullPath(fileOrDirectory.path);
|
||||
mapNewLeaves.set(path, true);
|
||||
// If its a change
|
||||
const currentEntry = this.fs.get(path);
|
||||
if (currentEntry) {
|
||||
if (isFile(currentEntry)) {
|
||||
if (isString(fileOrDirectory.content)) {
|
||||
// Update file
|
||||
if (currentEntry.content !== fileOrDirectory.content) {
|
||||
currentEntry.content = fileOrDirectory.content;
|
||||
if (options && options.invokeDirectoryWatcherInsteadOfFileChanged) {
|
||||
this.invokeDirectoryWatcher(getDirectoryPath(currentEntry.fullPath), currentEntry.fullPath);
|
||||
}
|
||||
else {
|
||||
this.invokeFileWatcher(currentEntry.fullPath, FileWatcherEventKind.Changed);
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
// TODO: Changing from file => folder
|
||||
}
|
||||
}
|
||||
else {
|
||||
// Folder
|
||||
if (isString(fileOrDirectory.content)) {
|
||||
// TODO: Changing from folder => file
|
||||
}
|
||||
else {
|
||||
// Folder update: Nothing to do.
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
this.ensureFileOrFolder(fileOrDirectory, options && options.ignoreWatchInvokedWithTriggerAsFileCreate);
|
||||
}
|
||||
}
|
||||
|
||||
if (!isNewFs) {
|
||||
this.fs.forEach((fileOrDirectory, path) => {
|
||||
// If this entry is not from the new file or folder
|
||||
if (!mapNewLeaves.get(path)) {
|
||||
// Leaf entries that arent in new list => remove these
|
||||
if (isFile(fileOrDirectory) || isFolder(fileOrDirectory) && fileOrDirectory.entries.length === 0) {
|
||||
this.removeFileOrFolder(fileOrDirectory, folder => !mapNewLeaves.get(folder.path));
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
renameFolder(folderName: string, newFolderName: string) {
|
||||
const fullPath = getNormalizedAbsolutePath(folderName, this.currentDirectory);
|
||||
const path = this.toPath(fullPath);
|
||||
const folder = this.fs.get(path) as Folder;
|
||||
Debug.assert(!!folder);
|
||||
|
||||
// Only remove the folder
|
||||
this.removeFileOrFolder(folder, returnFalse, /*isRenaming*/ true);
|
||||
|
||||
// Add updated folder with new folder name
|
||||
const newFullPath = getNormalizedAbsolutePath(newFolderName, this.currentDirectory);
|
||||
const newFolder = this.toFolder(newFullPath);
|
||||
const newPath = newFolder.path;
|
||||
const basePath = getDirectoryPath(path);
|
||||
Debug.assert(basePath !== path);
|
||||
Debug.assert(basePath === getDirectoryPath(newPath));
|
||||
const baseFolder = this.fs.get(basePath) as Folder;
|
||||
this.addFileOrFolderInFolder(baseFolder, newFolder);
|
||||
|
||||
// Invoke watches for files in the folder as deleted (from old path)
|
||||
for (const entry of folder.entries) {
|
||||
Debug.assert(isFile(entry));
|
||||
this.fs.delete(entry.path);
|
||||
this.invokeFileWatcher(entry.fullPath, FileWatcherEventKind.Deleted);
|
||||
|
||||
entry.fullPath = combinePaths(newFullPath, getBaseFileName(entry.fullPath));
|
||||
entry.path = this.toPath(entry.fullPath);
|
||||
newFolder.entries.push(entry);
|
||||
this.fs.set(entry.path, entry);
|
||||
this.invokeFileWatcher(entry.fullPath, FileWatcherEventKind.Created);
|
||||
}
|
||||
}
|
||||
|
||||
ensureFileOrFolder(fileOrDirectory: FileOrFolder, ignoreWatchInvokedWithTriggerAsFileCreate?: boolean) {
|
||||
if (isString(fileOrDirectory.content)) {
|
||||
const file = this.toFile(fileOrDirectory);
|
||||
Debug.assert(!this.fs.get(file.path));
|
||||
const baseFolder = this.ensureFolder(getDirectoryPath(file.fullPath));
|
||||
this.addFileOrFolderInFolder(baseFolder, file, ignoreWatchInvokedWithTriggerAsFileCreate);
|
||||
}
|
||||
else {
|
||||
const fullPath = getNormalizedAbsolutePath(fileOrDirectory.path, this.currentDirectory);
|
||||
this.ensureFolder(fullPath);
|
||||
}
|
||||
}
|
||||
|
||||
private ensureFolder(fullPath: string): Folder {
|
||||
const path = this.toPath(fullPath);
|
||||
let folder = this.fs.get(path) as Folder;
|
||||
if (!folder) {
|
||||
folder = this.toFolder(fullPath);
|
||||
const baseFullPath = getDirectoryPath(fullPath);
|
||||
if (fullPath !== baseFullPath) {
|
||||
// Add folder in the base folder
|
||||
const baseFolder = this.ensureFolder(baseFullPath);
|
||||
this.addFileOrFolderInFolder(baseFolder, folder);
|
||||
}
|
||||
else {
|
||||
// root folder
|
||||
Debug.assert(this.fs.size === 0);
|
||||
this.fs.set(path, folder);
|
||||
}
|
||||
}
|
||||
Debug.assert(isFolder(folder));
|
||||
return folder;
|
||||
}
|
||||
|
||||
private addFileOrFolderInFolder(folder: Folder, fileOrDirectory: File | Folder, ignoreWatch?: boolean) {
|
||||
folder.entries.push(fileOrDirectory);
|
||||
this.fs.set(fileOrDirectory.path, fileOrDirectory);
|
||||
|
||||
if (ignoreWatch) {
|
||||
return;
|
||||
}
|
||||
if (isFile(fileOrDirectory)) {
|
||||
this.invokeFileWatcher(fileOrDirectory.fullPath, FileWatcherEventKind.Created);
|
||||
}
|
||||
this.invokeDirectoryWatcher(folder.fullPath, fileOrDirectory.fullPath);
|
||||
}
|
||||
|
||||
private removeFileOrFolder(fileOrDirectory: File | Folder, isRemovableLeafFolder: (folder: Folder) => boolean, isRenaming?: boolean) {
|
||||
const basePath = getDirectoryPath(fileOrDirectory.path);
|
||||
const baseFolder = this.fs.get(basePath) as Folder;
|
||||
if (basePath !== fileOrDirectory.path) {
|
||||
Debug.assert(!!baseFolder);
|
||||
filterMutate(baseFolder.entries, entry => entry !== fileOrDirectory);
|
||||
}
|
||||
this.fs.delete(fileOrDirectory.path);
|
||||
|
||||
if (isFile(fileOrDirectory)) {
|
||||
this.invokeFileWatcher(fileOrDirectory.fullPath, FileWatcherEventKind.Deleted);
|
||||
}
|
||||
else {
|
||||
Debug.assert(fileOrDirectory.entries.length === 0 || isRenaming);
|
||||
const relativePath = this.getRelativePathToDirectory(fileOrDirectory.fullPath, fileOrDirectory.fullPath);
|
||||
// Invoke directory and recursive directory watcher for the folder
|
||||
// Here we arent invoking recursive directory watchers for the base folders
|
||||
// since that is something we would want to do for both file as well as folder we are deleting
|
||||
invokeWatcherCallbacks(this.watchedDirectories.get(fileOrDirectory.path), cb => this.directoryCallback(cb, relativePath));
|
||||
invokeWatcherCallbacks(this.watchedDirectoriesRecursive.get(fileOrDirectory.path), cb => this.directoryCallback(cb, relativePath));
|
||||
}
|
||||
|
||||
if (basePath !== fileOrDirectory.path) {
|
||||
if (baseFolder.entries.length === 0 && isRemovableLeafFolder(baseFolder)) {
|
||||
this.removeFileOrFolder(baseFolder, isRemovableLeafFolder);
|
||||
}
|
||||
else {
|
||||
this.invokeRecursiveDirectoryWatcher(baseFolder.fullPath, fileOrDirectory.fullPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private invokeFileWatcher(fileFullPath: string, eventKind: FileWatcherEventKind) {
|
||||
const callbacks = this.watchedFiles.get(this.toPath(fileFullPath));
|
||||
invokeWatcherCallbacks(callbacks, ({ cb, fileName }) => cb(fileName, eventKind));
|
||||
}
|
||||
|
||||
private getRelativePathToDirectory(directoryFullPath: string, fileFullPath: string) {
|
||||
return getRelativePathToDirectoryOrUrl(directoryFullPath, fileFullPath, this.currentDirectory, this.getCanonicalFileName, /*isAbsolutePathAnUrl*/ false);
|
||||
}
|
||||
|
||||
/**
|
||||
* This will call the directory watcher for the folderFullPath and recursive directory watchers for this and base folders
|
||||
*/
|
||||
private invokeDirectoryWatcher(folderFullPath: string, fileName: string) {
|
||||
const relativePath = this.getRelativePathToDirectory(folderFullPath, fileName);
|
||||
invokeWatcherCallbacks(this.watchedDirectories.get(this.toPath(folderFullPath)), cb => this.directoryCallback(cb, relativePath));
|
||||
this.invokeRecursiveDirectoryWatcher(folderFullPath, fileName);
|
||||
}
|
||||
|
||||
private directoryCallback({ cb, directoryName }: TestDirectoryWatcher, relativePath: string) {
|
||||
cb(combinePaths(directoryName, relativePath));
|
||||
}
|
||||
|
||||
/**
|
||||
* This will call the recursive directory watcher for this directory as well as all the base directories
|
||||
*/
|
||||
private invokeRecursiveDirectoryWatcher(fullPath: string, fileName: string) {
|
||||
const relativePath = this.getRelativePathToDirectory(fullPath, fileName);
|
||||
invokeWatcherCallbacks(this.watchedDirectoriesRecursive.get(this.toPath(fullPath)), cb => this.directoryCallback(cb, relativePath));
|
||||
const basePath = getDirectoryPath(fullPath);
|
||||
if (this.getCanonicalFileName(fullPath) !== this.getCanonicalFileName(basePath)) {
|
||||
this.invokeRecursiveDirectoryWatcher(basePath, fileName);
|
||||
}
|
||||
}
|
||||
|
||||
private toFile(fileOrDirectory: FileOrFolder): File {
|
||||
const fullPath = getNormalizedAbsolutePath(fileOrDirectory.path, this.currentDirectory);
|
||||
return {
|
||||
path: this.toPath(fullPath),
|
||||
content: fileOrDirectory.content,
|
||||
fullPath,
|
||||
fileSize: fileOrDirectory.fileSize
|
||||
};
|
||||
}
|
||||
|
||||
private toFolder(path: string): Folder {
|
||||
const fullPath = getNormalizedAbsolutePath(path, this.currentDirectory);
|
||||
return {
|
||||
path: this.toPath(fullPath),
|
||||
entries: [],
|
||||
fullPath
|
||||
};
|
||||
}
|
||||
|
||||
fileExists(s: string) {
|
||||
const path = this.toFullPath(s);
|
||||
return isFile(this.fs.get(path));
|
||||
}
|
||||
|
||||
readFile(s: string) {
|
||||
const fsEntry = this.fs.get(this.toFullPath(s));
|
||||
return isFile(fsEntry) ? fsEntry.content : undefined;
|
||||
}
|
||||
|
||||
getFileSize(s: string) {
|
||||
const path = this.toFullPath(s);
|
||||
const entry = this.fs.get(path);
|
||||
if (isFile(entry)) {
|
||||
return entry.fileSize ? entry.fileSize : entry.content.length;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
directoryExists(s: string) {
|
||||
const path = this.toFullPath(s);
|
||||
return isFolder(this.fs.get(path));
|
||||
}
|
||||
|
||||
getDirectories(s: string) {
|
||||
const path = this.toFullPath(s);
|
||||
const folder = this.fs.get(path);
|
||||
if (isFolder(folder)) {
|
||||
return mapDefined(folder.entries, entry => isFolder(entry) ? getBaseFileName(entry.fullPath) : undefined);
|
||||
}
|
||||
Debug.fail(folder ? "getDirectories called on file" : "getDirectories called on missing folder");
|
||||
return [];
|
||||
}
|
||||
|
||||
readDirectory(path: string, extensions?: ReadonlyArray<string>, exclude?: ReadonlyArray<string>, include?: ReadonlyArray<string>, depth?: number): string[] {
|
||||
return ts.matchFiles(this.toNormalizedAbsolutePath(path), extensions, exclude, include, this.useCaseSensitiveFileNames, this.getCurrentDirectory(), depth, (dir) => {
|
||||
const directories: string[] = [];
|
||||
const files: string[] = [];
|
||||
const dirEntry = this.fs.get(this.toPath(dir));
|
||||
if (isFolder(dirEntry)) {
|
||||
dirEntry.entries.forEach((entry) => {
|
||||
if (isFolder(entry)) {
|
||||
directories.push(getBaseFileName(entry.fullPath));
|
||||
}
|
||||
else if (isFile(entry)) {
|
||||
files.push(getBaseFileName(entry.fullPath));
|
||||
}
|
||||
else {
|
||||
Debug.fail("Unknown entry");
|
||||
}
|
||||
});
|
||||
}
|
||||
return { directories, files };
|
||||
});
|
||||
}
|
||||
|
||||
watchDirectory(directoryName: string, cb: DirectoryWatcherCallback, recursive: boolean): FileWatcher {
|
||||
const path = this.toFullPath(directoryName);
|
||||
const map = recursive ? this.watchedDirectoriesRecursive : this.watchedDirectories;
|
||||
const callback: TestDirectoryWatcher = {
|
||||
cb,
|
||||
directoryName
|
||||
};
|
||||
map.add(path, callback);
|
||||
return {
|
||||
close: () => map.remove(path, callback)
|
||||
};
|
||||
}
|
||||
|
||||
createHash(s: string): string {
|
||||
return Harness.mockHash(s);
|
||||
}
|
||||
|
||||
watchFile(fileName: string, cb: FileWatcherCallback) {
|
||||
const path = this.toFullPath(fileName);
|
||||
const callback: TestFileWatcher = { fileName, cb };
|
||||
this.watchedFiles.add(path, callback);
|
||||
return { close: () => this.watchedFiles.remove(path, callback) };
|
||||
}
|
||||
|
||||
// TOOD: record and invoke callbacks to simulate timer events
|
||||
setTimeout(callback: TimeOutCallback, _time: number, ...args: any[]) {
|
||||
return this.timeoutCallbacks.register(callback, args);
|
||||
}
|
||||
|
||||
getNextTimeoutId() {
|
||||
return this.timeoutCallbacks.getNextId();
|
||||
}
|
||||
|
||||
clearTimeout(timeoutId: any): void {
|
||||
this.timeoutCallbacks.unregister(timeoutId);
|
||||
}
|
||||
|
||||
checkTimeoutQueueLengthAndRun(expected: number) {
|
||||
this.checkTimeoutQueueLength(expected);
|
||||
this.runQueuedTimeoutCallbacks();
|
||||
}
|
||||
|
||||
checkTimeoutQueueLength(expected: number) {
|
||||
const callbacksCount = this.timeoutCallbacks.count();
|
||||
assert.equal(callbacksCount, expected, `expected ${expected} timeout callbacks queued but found ${callbacksCount}.`);
|
||||
}
|
||||
|
||||
runQueuedTimeoutCallbacks(timeoutId?: number) {
|
||||
try {
|
||||
this.timeoutCallbacks.invoke(timeoutId);
|
||||
}
|
||||
catch (e) {
|
||||
if (e.message === this.existMessage) {
|
||||
return;
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
runQueuedImmediateCallbacks() {
|
||||
this.immediateCallbacks.invoke();
|
||||
}
|
||||
|
||||
setImmediate(callback: TimeOutCallback, _time: number, ...args: any[]) {
|
||||
return this.immediateCallbacks.register(callback, args);
|
||||
}
|
||||
|
||||
clearImmediate(timeoutId: any): void {
|
||||
this.immediateCallbacks.unregister(timeoutId);
|
||||
}
|
||||
|
||||
createDirectory(directoryName: string): void {
|
||||
const folder = this.toFolder(directoryName);
|
||||
|
||||
// base folder has to be present
|
||||
const base = getDirectoryPath(folder.path);
|
||||
const baseFolder = this.fs.get(base) as Folder;
|
||||
Debug.assert(isFolder(baseFolder));
|
||||
|
||||
Debug.assert(!this.fs.get(folder.path));
|
||||
this.addFileOrFolderInFolder(baseFolder, folder);
|
||||
}
|
||||
|
||||
writeFile(path: string, content: string): void {
|
||||
const file = this.toFile({ path, content });
|
||||
|
||||
// base folder has to be present
|
||||
const base = getDirectoryPath(file.path);
|
||||
const folder = this.fs.get(base) as Folder;
|
||||
Debug.assert(isFolder(folder));
|
||||
|
||||
this.addFileOrFolderInFolder(folder, file);
|
||||
}
|
||||
|
||||
write(message: string) {
|
||||
this.output.push(message);
|
||||
}
|
||||
|
||||
getOutput(): ReadonlyArray<string> {
|
||||
return this.output;
|
||||
}
|
||||
|
||||
clearOutput() {
|
||||
clear(this.output);
|
||||
}
|
||||
|
||||
readonly existMessage = "System Exit";
|
||||
exitCode: number;
|
||||
readonly resolvePath = (s: string) => s;
|
||||
readonly getExecutingFilePath = () => this.executingFilePath;
|
||||
readonly getCurrentDirectory = () => this.currentDirectory;
|
||||
exit(exitCode?: number) {
|
||||
this.exitCode = exitCode;
|
||||
throw new Error(this.existMessage);
|
||||
}
|
||||
readonly getEnvironmentVariable = notImplemented;
|
||||
}
|
||||
}
|
||||
+106
-16
@@ -109,6 +109,11 @@ namespace ts.server {
|
||||
"smart": IndentStyle.Smart
|
||||
});
|
||||
|
||||
export interface TypesMapFile {
|
||||
typesMap: SafeList;
|
||||
simpleMap: { [libName: string]: string };
|
||||
}
|
||||
|
||||
/**
|
||||
* How to understand this block:
|
||||
* * The 'match' property is a regexp that matches a filename.
|
||||
@@ -345,6 +350,7 @@ namespace ts.server {
|
||||
globalPlugins?: ReadonlyArray<string>;
|
||||
pluginProbeLocations?: ReadonlyArray<string>;
|
||||
allowLocalPluginLoads?: boolean;
|
||||
typesMapLocation?: string;
|
||||
}
|
||||
|
||||
export class ProjectService {
|
||||
@@ -387,6 +393,7 @@ namespace ts.server {
|
||||
|
||||
private readonly hostConfiguration: HostConfiguration;
|
||||
private safelist: SafeList = defaultTypeSafeList;
|
||||
private legacySafelist: { [key: string]: string } = {};
|
||||
|
||||
private changedFiles: ScriptInfo[];
|
||||
|
||||
@@ -406,6 +413,7 @@ namespace ts.server {
|
||||
public readonly globalPlugins: ReadonlyArray<string>;
|
||||
public readonly pluginProbeLocations: ReadonlyArray<string>;
|
||||
public readonly allowLocalPluginLoads: boolean;
|
||||
public readonly typesMapLocation: string | undefined;
|
||||
|
||||
/** Tracks projects that we have already sent telemetry for. */
|
||||
private readonly seenProjects = createMap<true>();
|
||||
@@ -422,6 +430,7 @@ namespace ts.server {
|
||||
this.globalPlugins = opts.globalPlugins || emptyArray;
|
||||
this.pluginProbeLocations = opts.pluginProbeLocations || emptyArray;
|
||||
this.allowLocalPluginLoads = !!opts.allowLocalPluginLoads;
|
||||
this.typesMapLocation = (opts.typesMapLocation === undefined) ? combinePaths(this.getExecutingFilePath(), "../typesMap.json") : opts.typesMapLocation;
|
||||
|
||||
Debug.assert(!!this.host.createHash, "'ServerHost.createHash' is required for ProjectService");
|
||||
|
||||
@@ -429,6 +438,13 @@ namespace ts.server {
|
||||
this.directoryWatchers = new DirectoryWatchers(this);
|
||||
this.throttledOperations = new ThrottledOperations(this.host);
|
||||
|
||||
if (this.typesMapLocation) {
|
||||
this.loadTypesMap();
|
||||
}
|
||||
else {
|
||||
this.logger.info("No types map provided; using the default");
|
||||
}
|
||||
|
||||
this.typingsInstaller.attach(this);
|
||||
|
||||
this.typingsCache = new TypingsCache(this.typingsInstaller);
|
||||
@@ -466,6 +482,39 @@ namespace ts.server {
|
||||
this.eventHandler(event);
|
||||
}
|
||||
|
||||
/*@internal*/
|
||||
getNormalizedAbsolutePath(fileName: string) {
|
||||
return getNormalizedAbsolutePath(fileName, this.host.getCurrentDirectory());
|
||||
}
|
||||
|
||||
/*@internal*/
|
||||
getExecutingFilePath() {
|
||||
return this.getNormalizedAbsolutePath(this.host.getExecutingFilePath());
|
||||
}
|
||||
|
||||
private loadTypesMap() {
|
||||
try {
|
||||
const fileContent = this.host.readFile(this.typesMapLocation);
|
||||
if (fileContent === undefined) {
|
||||
this.logger.info(`Provided types map file "${this.typesMapLocation}" doesn't exist`);
|
||||
return;
|
||||
}
|
||||
const raw: TypesMapFile = JSON.parse(fileContent);
|
||||
// Parse the regexps
|
||||
for (const k of Object.keys(raw.typesMap)) {
|
||||
raw.typesMap[k].match = new RegExp(raw.typesMap[k].match as {} as string, "i");
|
||||
}
|
||||
// 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 = {};
|
||||
}
|
||||
}
|
||||
|
||||
updateTypingsForProject(response: SetTypings | InvalidateCachedTypings): void {
|
||||
const project = this.findProject(response.projectName);
|
||||
if (!project) {
|
||||
@@ -1085,7 +1134,7 @@ namespace ts.server {
|
||||
return false;
|
||||
}
|
||||
|
||||
private createAndAddExternalProject(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,
|
||||
@@ -1094,6 +1143,7 @@ namespace ts.server {
|
||||
compilerOptions,
|
||||
/*languageServiceEnabled*/ !this.exceededTotalSizeLimitForNonTsFiles(projectFileName, compilerOptions, files, externalFilePropertyReader),
|
||||
options.compileOnSave === undefined ? true : options.compileOnSave);
|
||||
project.excludedFiles = excludedFiles;
|
||||
|
||||
this.addFilesToProjectAndUpdateGraph(project, files, externalFilePropertyReader, /*clientFileName*/ undefined, typeAcquisition, /*configFileErrors*/ undefined);
|
||||
this.externalProjects.push(project);
|
||||
@@ -1737,25 +1787,33 @@ namespace ts.server {
|
||||
this.safelist = raw;
|
||||
}
|
||||
|
||||
applySafeList(proj: protocol.ExternalProject): void {
|
||||
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 excludedFiles: NormalizedPath[] = [];
|
||||
|
||||
const normalizedNames = rootFiles.map(f => normalizeSlashes(f.fileName));
|
||||
const normalizedNames = rootFiles.map(f => normalizeSlashes(f.fileName)) as NormalizedPath[];
|
||||
|
||||
for (const name of Object.keys(this.safelist)) {
|
||||
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) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1793,16 +1851,42 @@ 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"));
|
||||
proj.rootFiles = proj.rootFiles.filter((_file, index) => !excludeRegexes.some(re => re.test(normalizedNames[index])));
|
||||
const filesToKeep: ts.server.protocol.ExternalFile[] = [];
|
||||
for (let i = 0; i < proj.rootFiles.length; i++) {
|
||||
if (excludeRegexes.some(re => re.test(normalizedNames[i]))) {
|
||||
excludedFiles.push(normalizedNames[i]);
|
||||
}
|
||||
else {
|
||||
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;
|
||||
return excludedFiles;
|
||||
}
|
||||
|
||||
openExternalProject(proj: protocol.ExternalProject, suppressRefreshOfInferredProjects = false): void {
|
||||
@@ -1812,8 +1896,14 @@ 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));
|
||||
}
|
||||
|
||||
this.applySafeList(proj);
|
||||
const excludedFiles = this.applySafeList(proj);
|
||||
|
||||
let tsConfigFiles: NormalizedPath[];
|
||||
const rootFiles: protocol.ExternalFile[] = [];
|
||||
@@ -1906,7 +1996,7 @@ namespace ts.server {
|
||||
else {
|
||||
// no config files - remove the item from the collection
|
||||
this.externalProjectToConfiguredProjectMap.delete(proj.projectFileName);
|
||||
this.createAndAddExternalProject(proj.projectFileName, rootFiles, proj.options, proj.typeAcquisition);
|
||||
this.createExternalProject(proj.projectFileName, rootFiles, proj.options, proj.typeAcquisition, excludedFiles);
|
||||
}
|
||||
if (!suppressRefreshOfInferredProjects) {
|
||||
this.refreshInferredProjects();
|
||||
|
||||
+10
-20
@@ -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<Diagnostic>;
|
||||
@@ -1185,6 +1190,7 @@ namespace ts.server {
|
||||
* These are created only if a host explicitly calls `openExternalProject`.
|
||||
*/
|
||||
export class ExternalProject extends Project {
|
||||
excludedFiles: ReadonlyArray<NormalizedPath> = [];
|
||||
private typeAcquisition: TypeAcquisition;
|
||||
constructor(public externalProjectName: string,
|
||||
projectService: ProjectService,
|
||||
@@ -1216,26 +1222,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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,487 @@
|
||||
{
|
||||
"typesMap": {
|
||||
"jquery": {
|
||||
"match": "jquery(-(\\.?\\d+)+)?(\\.intellisense)?(\\.min)?\\.js$",
|
||||
"types": ["jquery"]
|
||||
},
|
||||
"WinJS": {
|
||||
"match": "^(.*\\/winjs-[.\\d]+)\\/js\\/base\\.js$",
|
||||
"exclude": [["^", 1, "/.*"]],
|
||||
"types": ["winjs"]
|
||||
},
|
||||
"Kendo": {
|
||||
"match": "^(.*\\/kendo)\\/kendo\\.all\\.min\\.js$",
|
||||
"exclude": [["^", 1, "/.*"]],
|
||||
"types": ["kendo-ui"]
|
||||
},
|
||||
"Office Nuget": {
|
||||
"match": "^(.*\\/office\\/1)\\/excel-\\d+\\.debug\\.js$",
|
||||
"exclude": [["^", 1, "/.*"]],
|
||||
"types": ["office"]
|
||||
},
|
||||
"Minified files": {
|
||||
"match": "^(.+\\.min\\.js)$",
|
||||
"exclude": [["^", 1, "$"]]
|
||||
}
|
||||
},
|
||||
"simpleMap": {
|
||||
"accounting": "accounting",
|
||||
"ace.js": "ace",
|
||||
"ag-grid": "ag-grid",
|
||||
"alertify": "alertify",
|
||||
"alt": "alt",
|
||||
"amcharts.js": "amcharts",
|
||||
"amplify": "amplifyjs",
|
||||
"angular": "angular",
|
||||
"angular-bootstrap-lightbox": "angular-bootstrap-lightbox",
|
||||
"angular-cookie": "angular-cookie",
|
||||
"angular-file-upload": "angular-file-upload",
|
||||
"angularfire": "angularfire",
|
||||
"angular-gettext": "angular-gettext",
|
||||
"angular-google-analytics": "angular-google-analytics",
|
||||
"angular-local-storage": "angular-local-storage",
|
||||
"angularLocalStorage": "angularLocalStorage",
|
||||
"angular-scroll": "angular-scroll",
|
||||
"angular-spinner": "angular-spinner",
|
||||
"angular-strap": "angular-strap",
|
||||
"angulartics": "angulartics",
|
||||
"angular-toastr": "angular-toastr",
|
||||
"angular-translate": "angular-translate",
|
||||
"angular-ui-router": "angular-ui-router",
|
||||
"angular-ui-tree": "angular-ui-tree",
|
||||
"angular-wizard": "angular-wizard",
|
||||
"async": "async",
|
||||
"atmosphere": "atmosphere",
|
||||
"aws-sdk": "aws-sdk",
|
||||
"aws-sdk-js": "aws-sdk",
|
||||
"axios": "axios",
|
||||
"backbone": "backbone",
|
||||
"backbone.layoutmanager": "backbone.layoutmanager",
|
||||
"backbone.paginator": "backbone.paginator",
|
||||
"backbone.radio": "backbone.radio",
|
||||
"backbone-associations": "backbone-associations",
|
||||
"backbone-relational": "backbone-relational",
|
||||
"backgrid": "backgrid",
|
||||
"Bacon": "baconjs",
|
||||
"benchmark": "benchmark",
|
||||
"blazy": "blazy",
|
||||
"bliss": "blissfuljs",
|
||||
"bluebird": "bluebird",
|
||||
"body-parser": "body-parser",
|
||||
"bootbox": "bootbox",
|
||||
"bootstrap": "bootstrap",
|
||||
"bootstrap-editable": "x-editable",
|
||||
"bootstrap-maxlength": "bootstrap-maxlength",
|
||||
"bootstrap-notify": "bootstrap-notify",
|
||||
"bootstrap-slider": "bootstrap-slider",
|
||||
"bootstrap-switch": "bootstrap-switch",
|
||||
"bowser": "bowser",
|
||||
"breeze": "breeze",
|
||||
"browserify": "browserify",
|
||||
"bson": "bson",
|
||||
"c3": "c3",
|
||||
"canvasjs": "canvasjs",
|
||||
"chai": "chai",
|
||||
"chalk": "chalk",
|
||||
"chance": "chance",
|
||||
"chartist": "chartist",
|
||||
"cheerio": "cheerio",
|
||||
"chokidar": "chokidar",
|
||||
"chosen.jquery": "chosen",
|
||||
"chroma": "chroma-js",
|
||||
"ckeditor.js": "ckeditor",
|
||||
"cli-color": "cli-color",
|
||||
"clipboard": "clipboard",
|
||||
"codemirror": "codemirror",
|
||||
"colors": "colors",
|
||||
"commander": "commander",
|
||||
"commonmark": "commonmark",
|
||||
"compression": "compression",
|
||||
"confidence": "confidence",
|
||||
"connect": "connect",
|
||||
"Control.FullScreen": "leaflet.fullscreen",
|
||||
"cookie": "cookie",
|
||||
"cookie-parser": "cookie-parser",
|
||||
"cookies": "cookies",
|
||||
"core": "core-js",
|
||||
"core-js": "core-js",
|
||||
"crossfilter": "crossfilter",
|
||||
"crossroads": "crossroads",
|
||||
"css": "css",
|
||||
"ct-ui-router-extras": "ui-router-extras",
|
||||
"d3": "d3",
|
||||
"dagre-d3": "dagre-d3",
|
||||
"dat.gui": "dat-gui",
|
||||
"debug": "debug",
|
||||
"deep-diff": "deep-diff",
|
||||
"Dexie": "dexie",
|
||||
"dialogs": "angular-dialog-service",
|
||||
"dojo.js": "dojo",
|
||||
"doT": "dot",
|
||||
"dragula": "dragula",
|
||||
"drop": "drop",
|
||||
"dropbox": "dropboxjs",
|
||||
"dropzone": "dropzone",
|
||||
"Dts Name": "Dts Name",
|
||||
"dust-core": "dustjs-linkedin",
|
||||
"easeljs": "easeljs",
|
||||
"ejs": "ejs",
|
||||
"ember": "ember",
|
||||
"envify": "envify",
|
||||
"epiceditor": "epiceditor",
|
||||
"es6-promise": "es6-promise",
|
||||
"ES6-Promise": "es6-promise",
|
||||
"es6-shim": "es6-shim",
|
||||
"expect": "expect",
|
||||
"express": "express",
|
||||
"express-session": "express-session",
|
||||
"ext-all.js": "extjs",
|
||||
"extend": "extend",
|
||||
"fabric": "fabricjs",
|
||||
"faker": "faker",
|
||||
"fastclick": "fastclick",
|
||||
"favico": "favico.js",
|
||||
"featherlight": "featherlight",
|
||||
"FileSaver": "FileSaver",
|
||||
"fingerprint": "fingerprintjs",
|
||||
"fixed-data-table": "fixed-data-table",
|
||||
"flickity.pkgd": "flickity",
|
||||
"flight": "flight",
|
||||
"flow": "flowjs",
|
||||
"Flux": "flux",
|
||||
"formly": "angular-formly",
|
||||
"foundation": "foundation",
|
||||
"fpsmeter": "fpsmeter",
|
||||
"fuse": "fuse",
|
||||
"generator": "yeoman-generator",
|
||||
"gl-matrix": "gl-matrix",
|
||||
"globalize": "globalize",
|
||||
"graceful-fs": "graceful-fs",
|
||||
"gridstack": "gridstack",
|
||||
"gulp": "gulp",
|
||||
"gulp-rename": "gulp-rename",
|
||||
"gulp-uglify": "gulp-uglify",
|
||||
"gulp-util": "gulp-util",
|
||||
"hammer": "hammerjs",
|
||||
"handlebars": "handlebars",
|
||||
"hasher": "hasher",
|
||||
"he": "he",
|
||||
"hello.all": "hellojs",
|
||||
"highcharts.js": "highcharts",
|
||||
"highlight": "highlightjs",
|
||||
"history": "history",
|
||||
"History": "history",
|
||||
"hopscotch": "hopscotch",
|
||||
"hotkeys": "angular-hotkeys",
|
||||
"html2canvas": "html2canvas",
|
||||
"humane": "humane",
|
||||
"i18next": "i18next",
|
||||
"icheck": "icheck",
|
||||
"impress": "impress",
|
||||
"incremental-dom": "incremental-dom",
|
||||
"Inquirer": "inquirer",
|
||||
"insight": "insight",
|
||||
"interact": "interactjs",
|
||||
"intercom": "intercomjs",
|
||||
"intro": "intro.js",
|
||||
"ion.rangeSlider": "ion.rangeSlider",
|
||||
"ionic": "ionic",
|
||||
"is": "is_js",
|
||||
"iscroll": "iscroll",
|
||||
"jade": "jade",
|
||||
"jasmine": "jasmine",
|
||||
"joint": "jointjs",
|
||||
"jquery": "jquery",
|
||||
"jquery.address": "jquery.address",
|
||||
"jquery.are-you-sure": "jquery.are-you-sure",
|
||||
"jquery.blockUI": "jquery.blockUI",
|
||||
"jquery.bootstrap.wizard": "jquery.bootstrap.wizard",
|
||||
"jquery.bootstrap-touchspin": "bootstrap-touchspin",
|
||||
"jquery.color": "jquery.color",
|
||||
"jquery.colorbox": "jquery.colorbox",
|
||||
"jquery.contextMenu": "jquery.contextMenu",
|
||||
"jquery.cookie": "jquery.cookie",
|
||||
"jquery.customSelect": "jquery.customSelect",
|
||||
"jquery.cycle.all": "jquery.cycle",
|
||||
"jquery.cycle2": "jquery.cycle2",
|
||||
"jquery.dataTables": "jquery.dataTables",
|
||||
"jquery.dropotron": "jquery.dropotron",
|
||||
"jquery.fancybox.pack.js": "fancybox",
|
||||
"jquery.fancytree-all": "jquery.fancytree",
|
||||
"jquery.fileupload": "jquery.fileupload",
|
||||
"jquery.flot": "flot",
|
||||
"jquery.form": "jquery.form",
|
||||
"jquery.gridster": "jquery.gridster",
|
||||
"jquery.handsontable.full": "jquery-handsontable",
|
||||
"jquery.joyride": "jquery.joyride",
|
||||
"jquery.jqGrid": "jqgrid",
|
||||
"jquery.mmenu": "jquery.mmenu",
|
||||
"jquery.mockjax": "jquery-mockjax",
|
||||
"jquery.noty": "jquery.noty",
|
||||
"jquery.payment": "jquery.payment",
|
||||
"jquery.pjax": "jquery.pjax",
|
||||
"jquery.placeholder": "jquery.placeholder",
|
||||
"jquery.qrcode": "jquery.qrcode",
|
||||
"jquery.qtip": "qtip2",
|
||||
"jquery.raty": "raty",
|
||||
"jquery.scrollTo": "jquery.scrollTo",
|
||||
"jquery.signalR": "signalr",
|
||||
"jquery.simplemodal": "jquery.simplemodal",
|
||||
"jquery.timeago": "jquery.timeago",
|
||||
"jquery.tinyscrollbar": "jquery.tinyscrollbar",
|
||||
"jquery.tipsy": "jquery.tipsy",
|
||||
"jquery.tooltipster": "tooltipster",
|
||||
"jquery.transit": "jquery.transit",
|
||||
"jquery.uniform": "jquery.uniform",
|
||||
"jquery.watch": "watch",
|
||||
"jquery-sortable": "jquery-sortable",
|
||||
"jquery-ui": "jqueryui",
|
||||
"js.cookie": "js-cookie",
|
||||
"js-data": "js-data",
|
||||
"js-data-angular": "js-data-angular",
|
||||
"js-data-http": "js-data-http",
|
||||
"jsdom": "jsdom",
|
||||
"jsnlog": "jsnlog",
|
||||
"json5": "json5",
|
||||
"jspdf": "jspdf",
|
||||
"jsrender": "jsrender",
|
||||
"js-signals": "js-signals",
|
||||
"jstorage": "jstorage",
|
||||
"jstree": "jstree",
|
||||
"js-yaml": "js-yaml",
|
||||
"jszip": "jszip",
|
||||
"katex": "katex",
|
||||
"kefir": "kefir",
|
||||
"keymaster": "keymaster",
|
||||
"keypress": "keypress",
|
||||
"kinetic": "kineticjs",
|
||||
"knockback": "knockback",
|
||||
"knockout": "knockout",
|
||||
"knockout.mapping": "knockout.mapping",
|
||||
"knockout.validation": "knockout.validation",
|
||||
"knockout-paging": "knockout-paging",
|
||||
"knockout-pre-rendered": "knockout-pre-rendered",
|
||||
"ladda": "ladda",
|
||||
"later": "later",
|
||||
"lazy": "lazy.js",
|
||||
"Leaflet.Editable": "leaflet-editable",
|
||||
"leaflet.js": "leaflet",
|
||||
"less": "less",
|
||||
"linq": "linq",
|
||||
"loading-bar": "angular-loading-bar",
|
||||
"lodash": "lodash",
|
||||
"log4javascript": "log4javascript",
|
||||
"loglevel": "loglevel",
|
||||
"lokijs": "lokijs",
|
||||
"lovefield": "lovefield",
|
||||
"lunr": "lunr",
|
||||
"lz-string": "lz-string",
|
||||
"mailcheck": "mailcheck",
|
||||
"maquette": "maquette",
|
||||
"marked": "marked",
|
||||
"math": "mathjs",
|
||||
"MathJax.js": "mathjax",
|
||||
"matter": "matter-js",
|
||||
"md5": "blueimp-md5",
|
||||
"md5.js": "crypto-js",
|
||||
"messenger": "messenger",
|
||||
"method-override": "method-override",
|
||||
"minimatch": "minimatch",
|
||||
"minimist": "minimist",
|
||||
"mithril": "mithril",
|
||||
"mobile-detect": "mobile-detect",
|
||||
"mocha": "mocha",
|
||||
"mock-ajax": "jasmine-ajax",
|
||||
"modernizr": "modernizr",
|
||||
"Modernizr": "Modernizr",
|
||||
"moment": "moment",
|
||||
"moment-range": "moment-range",
|
||||
"moment-timezone": "moment-timezone",
|
||||
"mongoose": "mongoose",
|
||||
"morgan": "morgan",
|
||||
"mousetrap": "mousetrap",
|
||||
"ms": "ms",
|
||||
"mustache": "mustache",
|
||||
"native.history": "history",
|
||||
"nconf": "nconf",
|
||||
"ncp": "ncp",
|
||||
"nedb": "nedb",
|
||||
"ng-cordova": "ng-cordova",
|
||||
"ngDialog": "ng-dialog",
|
||||
"ng-flow-standalone": "ng-flow",
|
||||
"ng-grid": "ng-grid",
|
||||
"ng-i18next": "ng-i18next",
|
||||
"ng-table": "ng-table",
|
||||
"node_redis": "redis",
|
||||
"node-clone": "clone",
|
||||
"node-fs-extra": "fs-extra",
|
||||
"node-glob": "glob",
|
||||
"Nodemailer": "nodemailer",
|
||||
"node-mime": "mime",
|
||||
"node-mkdirp": "mkdirp",
|
||||
"node-mongodb-native": "mongodb",
|
||||
"node-mysql": "mysql",
|
||||
"node-open": "open",
|
||||
"node-optimist": "optimist",
|
||||
"node-progress": "progress",
|
||||
"node-semver": "semver",
|
||||
"node-tar": "tar",
|
||||
"node-uuid": "node-uuid",
|
||||
"node-xml2js": "xml2js",
|
||||
"nopt": "nopt",
|
||||
"notify": "notify",
|
||||
"nouislider": "nouislider",
|
||||
"npm": "npm",
|
||||
"nprogress": "nprogress",
|
||||
"numbro": "numbro",
|
||||
"numeral": "numeraljs",
|
||||
"nunjucks": "nunjucks",
|
||||
"nv.d3": "nvd3",
|
||||
"object-assign": "object-assign",
|
||||
"oboe-browser": "oboe",
|
||||
"office": "office-js",
|
||||
"offline": "offline-js",
|
||||
"onsenui": "onsenui",
|
||||
"OpenLayers.js": "openlayers",
|
||||
"openpgp": "openpgp",
|
||||
"p2": "p2",
|
||||
"packery.pkgd": "packery",
|
||||
"page": "page",
|
||||
"pako": "pako",
|
||||
"papaparse": "papaparse",
|
||||
"passport": "passport",
|
||||
"passport-local": "passport-local",
|
||||
"path": "pathjs",
|
||||
"peer": "peerjs",
|
||||
"peg": "pegjs",
|
||||
"photoswipe": "photoswipe",
|
||||
"picker.js": "pickadate",
|
||||
"pikaday": "pikaday",
|
||||
"pixi": "pixi.js",
|
||||
"platform": "platform",
|
||||
"Please": "pleasejs",
|
||||
"plottable": "plottable",
|
||||
"polymer": "polymer",
|
||||
"postal": "postal",
|
||||
"preloadjs": "preloadjs",
|
||||
"progress": "progress",
|
||||
"purify": "dompurify",
|
||||
"purl": "purl",
|
||||
"q": "q",
|
||||
"qs": "qs",
|
||||
"qunit": "qunit",
|
||||
"ractive": "ractive",
|
||||
"rangy-core": "rangy",
|
||||
"raphael": "raphael",
|
||||
"raven": "ravenjs",
|
||||
"react": "react",
|
||||
"react-bootstrap": "react-bootstrap",
|
||||
"react-intl": "react-intl",
|
||||
"react-redux": "react-redux",
|
||||
"ReactRouter": "react-router",
|
||||
"ready": "domready",
|
||||
"redux": "redux",
|
||||
"request": "request",
|
||||
"require": "require",
|
||||
"restangular": "restangular",
|
||||
"reveal": "reveal",
|
||||
"rickshaw": "rickshaw",
|
||||
"rimraf": "rimraf",
|
||||
"rivets": "rivets",
|
||||
"rx": "rx",
|
||||
"rx.angular": "rx-angular",
|
||||
"sammy": "sammyjs",
|
||||
"SAT": "sat",
|
||||
"sax-js": "sax",
|
||||
"screenfull": "screenfull",
|
||||
"seedrandom": "seedrandom",
|
||||
"select2": "select2",
|
||||
"selectize": "selectize",
|
||||
"serve-favicon": "serve-favicon",
|
||||
"serve-static": "serve-static",
|
||||
"shelljs": "shelljs",
|
||||
"should": "should",
|
||||
"showdown": "showdown",
|
||||
"sigma": "sigmajs",
|
||||
"signature_pad": "signature_pad",
|
||||
"sinon": "sinon",
|
||||
"sjcl": "sjcl",
|
||||
"slick": "slick-carousel",
|
||||
"smoothie": "smoothie",
|
||||
"socket.io": "socket.io",
|
||||
"socket.io-client": "socket.io-client",
|
||||
"sockjs": "sockjs-client",
|
||||
"sortable": "angular-ui-sortable",
|
||||
"soundjs": "soundjs",
|
||||
"source-map": "source-map",
|
||||
"spectrum": "spectrum",
|
||||
"spin": "spin",
|
||||
"sprintf": "sprintf",
|
||||
"stampit": "stampit",
|
||||
"state-machine": "state-machine",
|
||||
"Stats": "stats",
|
||||
"store": "storejs",
|
||||
"string": "string",
|
||||
"string_score": "string_score",
|
||||
"strophe": "strophe",
|
||||
"stylus": "stylus",
|
||||
"sugar": "sugar",
|
||||
"superagent": "superagent",
|
||||
"svg": "svgjs",
|
||||
"svg-injector": "svg-injector",
|
||||
"swfobject": "swfobject",
|
||||
"swig": "swig",
|
||||
"swipe": "swipe",
|
||||
"swiper": "swiper",
|
||||
"system.js": "systemjs",
|
||||
"tether": "tether",
|
||||
"three": "threejs",
|
||||
"through": "through",
|
||||
"through2": "through2",
|
||||
"timeline": "timelinejs",
|
||||
"tinycolor": "tinycolor",
|
||||
"tmhDynamicLocale": "angular-dynamic-locale",
|
||||
"toaster": "angularjs-toaster",
|
||||
"toastr": "toastr",
|
||||
"tracking": "tracking",
|
||||
"trunk8": "trunk8",
|
||||
"turf": "turf",
|
||||
"tweenjs": "tweenjs",
|
||||
"TweenMax": "gsap",
|
||||
"twig": "twig",
|
||||
"twix": "twix",
|
||||
"typeahead.bundle": "typeahead",
|
||||
"typescript": "typescript",
|
||||
"ui": "winjs",
|
||||
"ui-bootstrap-tpls": "angular-ui-bootstrap",
|
||||
"ui-grid": "ui-grid",
|
||||
"uikit": "uikit",
|
||||
"underscore": "underscore",
|
||||
"underscore.string": "underscore.string",
|
||||
"update-notifier": "update-notifier",
|
||||
"url": "jsurl",
|
||||
"UUID": "uuid",
|
||||
"validator": "validator",
|
||||
"vega": "vega",
|
||||
"vex": "vex-js",
|
||||
"video": "videojs",
|
||||
"vue": "vue",
|
||||
"vue-router": "vue-router",
|
||||
"webtorrent": "webtorrent",
|
||||
"when": "when",
|
||||
"winston": "winston",
|
||||
"wrench-js": "wrench",
|
||||
"ws": "ws",
|
||||
"xlsx": "xlsx",
|
||||
"xml2json": "x2js",
|
||||
"xmlbuilder-js": "xmlbuilder",
|
||||
"xregexp": "xregexp",
|
||||
"yargs": "yargs",
|
||||
"yosay": "yosay",
|
||||
"yui": "yui",
|
||||
"yui3": "yui",
|
||||
"zepto": "zepto",
|
||||
"ZeroClipboard": "zeroclipboard",
|
||||
"ZSchema-browser": "z-schema"
|
||||
}
|
||||
}
|
||||
@@ -161,9 +161,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);
|
||||
|
||||
@@ -172,6 +169,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`);
|
||||
}
|
||||
|
||||
@@ -172,7 +172,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) {
|
||||
|
||||
Reference in New Issue
Block a user