From 284d9f527c4162e3d044f93a5bfa3e1b1940fba2 Mon Sep 17 00:00:00 2001 From: Jason Ramsay Date: Sun, 21 Feb 2016 21:35:02 -0800 Subject: [PATCH 01/15] Salsa: JS support for discovering and acquiring d.ts files (Mostly isolating VS host changes from PR#6448) --- Jakefile.js | 1 + src/compiler/commandLineParser.ts | 27 +++ src/compiler/core.ts | 26 +++ src/compiler/diagnosticMessages.json | 4 + src/compiler/parser.ts | 24 +-- src/compiler/types.ts | 8 + src/services/jsTyping.ts | 286 +++++++++++++++++++++++++++ src/services/services.ts | 6 +- src/services/shims.ts | 47 ++++- src/services/tsconfig.json | 1 + src/services/utilities.ts | 15 ++ 11 files changed, 416 insertions(+), 29 deletions(-) create mode 100644 src/services/jsTyping.ts diff --git a/Jakefile.js b/Jakefile.js index 84248ca34d1..f0cc878ad98 100644 --- a/Jakefile.js +++ b/Jakefile.js @@ -927,6 +927,7 @@ var servicesLintTargets = [ "patternMatcher.ts", "services.ts", "shims.ts", + "jsTyping.ts" ].map(function (s) { return path.join(servicesDirectory, s); }); diff --git a/src/compiler/commandLineParser.ts b/src/compiler/commandLineParser.ts index 86d073f7d49..af7b3a747cb 100644 --- a/src/compiler/commandLineParser.ts +++ b/src/compiler/commandLineParser.ts @@ -537,6 +537,7 @@ namespace ts { return { options, fileNames: getFileNames(), + typingOptions: getTypingOptions(), errors }; @@ -601,6 +602,32 @@ namespace ts { } return fileNames; } + + function getTypingOptions(): TypingOptions { + const options: TypingOptions = getBaseFileName(configFileName) === "jsconfig.json" + ? { enableAutoDiscovery: true, include: [], exclude: [] } + : { enableAutoDiscovery: false, include: [], exclude: [] }; + const jsonTypingOptions = json["typingOptions"]; + if (jsonTypingOptions) { + for (const id in jsonTypingOptions) { + if (id === "enableAutoDiscovery") { + if (typeof jsonTypingOptions[id] === "boolean") { + options.enableAutoDiscovery = jsonTypingOptions[id]; + } + } + else if (id === "include") { + options.include = isArray(jsonTypingOptions[id]) ? jsonTypingOptions[id] : []; + } + else if (id === "exclude") { + options.exclude = isArray(jsonTypingOptions[id]) ? jsonTypingOptions[id] : []; + } + else { + errors.push(createCompilerDiagnostic(Diagnostics.Unknown_typing_option_0, id)); + } + } + } + return options; + } } export function convertCompilerOptionsFromJson(jsonOptions: any, basePath: string, configFileName?: string): { options: CompilerOptions, errors: Diagnostic[] } { diff --git a/src/compiler/core.ts b/src/compiler/core.ts index 702ded96a3f..59274201155 100644 --- a/src/compiler/core.ts +++ b/src/compiler/core.ts @@ -778,6 +778,32 @@ namespace ts { return pathLen > extLen && path.substr(pathLen - extLen, extLen) === extension; } + export function ensureScriptKind(fileName: string, scriptKind?: ScriptKind): ScriptKind { + // Using scriptKind as a condition handles both: + // - 'scriptKind' is unspecified and thus it is `undefined` + // - 'scriptKind' is set and it is `Unknown` (0) + // If the 'scriptKind' is 'undefined' or 'Unknown' then we attempt + // to get the ScriptKind from the file name. If it cannot be resolved + // from the file name then the default 'TS' script kind is returned. + return (scriptKind || getScriptKindFromFileName(fileName)) || ScriptKind.TS; + } + + export function getScriptKindFromFileName(fileName: string): ScriptKind { + const ext = fileName.substr(fileName.lastIndexOf(".")); + switch (ext.toLowerCase()) { + case ".js": + return ScriptKind.JS; + case ".jsx": + return ScriptKind.JSX; + case ".ts": + return ScriptKind.TS; + case ".tsx": + return ScriptKind.TSX; + default: + return ScriptKind.Unknown; + } + } + /** * List of supported extensions in order of file resolution precedence. */ diff --git a/src/compiler/diagnosticMessages.json b/src/compiler/diagnosticMessages.json index 70c6d8ff167..a63b1b36476 100644 --- a/src/compiler/diagnosticMessages.json +++ b/src/compiler/diagnosticMessages.json @@ -2800,5 +2800,9 @@ "'super' must be called before accessing 'this' in the constructor of a derived class.": { "category": "Error", "code": 17009 + }, + "Unknown typing option '{0}'.": { + "category": "Error", + "code": 17010 } } diff --git a/src/compiler/parser.ts b/src/compiler/parser.ts index db9639156a5..0a31c1bdebb 100644 --- a/src/compiler/parser.ts +++ b/src/compiler/parser.ts @@ -407,23 +407,6 @@ namespace ts { return result; } - /* @internal */ - export function getScriptKindFromFileName(fileName: string): ScriptKind { - const ext = fileName.substr(fileName.lastIndexOf(".")); - switch (ext.toLowerCase()) { - case ".js": - return ScriptKind.JS; - case ".jsx": - return ScriptKind.JSX; - case ".ts": - return ScriptKind.TS; - case ".tsx": - return ScriptKind.TSX; - default: - return ScriptKind.TS; - } - } - // Produces a new SourceFile for the 'newText' provided. The 'textChangeRange' parameter // indicates what changed between the 'text' that this SourceFile has and the 'newText'. // The SourceFile will be created with the compiler attempting to reuse as many nodes from @@ -551,12 +534,7 @@ namespace ts { let parseErrorBeforeNextFinishedNode = false; export function parseSourceFile(fileName: string, _sourceText: string, languageVersion: ScriptTarget, _syntaxCursor: IncrementalParser.SyntaxCursor, setParentNodes?: boolean, scriptKind?: ScriptKind): SourceFile { - // Using scriptKind as a condition handles both: - // - 'scriptKind' is unspecified and thus it is `undefined` - // - 'scriptKind' is set and it is `Unknown` (0) - // If the 'scriptKind' is 'undefined' or 'Unknown' then attempt - // to get the ScriptKind from the file name. - scriptKind = scriptKind ? scriptKind : getScriptKindFromFileName(fileName); + scriptKind = ensureScriptKind(fileName, scriptKind); initializeState(fileName, _sourceText, languageVersion, _syntaxCursor, scriptKind); diff --git a/src/compiler/types.ts b/src/compiler/types.ts index ca662d6ac46..c838a852647 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -2432,6 +2432,13 @@ namespace ts { [option: string]: string | number | boolean | TsConfigOnlyOptions; } + export interface TypingOptions { + enableAutoDiscovery?: boolean; + include?: string[]; + exclude?: string[]; + [option: string]: any; + } + export enum ModuleKind { None = 0, CommonJS = 1, @@ -2490,6 +2497,7 @@ namespace ts { export interface ParsedCommandLine { options: CompilerOptions; + typingOptions?: TypingOptions; fileNames: string[]; errors: Diagnostic[]; } diff --git a/src/services/jsTyping.ts b/src/services/jsTyping.ts new file mode 100644 index 00000000000..bf3c6b6cc9a --- /dev/null +++ b/src/services/jsTyping.ts @@ -0,0 +1,286 @@ +// Copyright (c) Microsoft. All rights reserved. Licensed under the Apache License, Version 2.0. +// See LICENSE.txt in the project root for complete license information. + +/// + +/* @internal */ +namespace ts.JsTyping { + + interface TypingResolutionHost { + directoryExists: (path: string) => boolean; + fileExists: (fileName: string) => boolean; + readFile: (path: string, encoding?: string) => string; + readDirectory: (path: string, extension?: string, exclude?: string[], depth?: number) => string[]; + }; + + // A map of loose file names to library names + // that we are confident require typings + let safeList: Map; + const notFoundTypingNames: string[] = []; + + function tryParseJson(jsonPath: string, host: TypingResolutionHost): any { + if (host.fileExists(jsonPath)) { + try { + // Strip out single-line comments + const contents = host.readFile(jsonPath).replace(/^\/\/(.*)$/gm, ""); + return JSON.parse(contents); + } + catch (e) { } + } + return undefined; + } + + function isTypingEnabled(options: TypingOptions): boolean { + if (options) { + if (options.enableAutoDiscovery || + (options.include && options.include.length > 0) || + (options.exclude && options.exclude.length > 0)) { + return true; + } + } + return false; + } + + /** + * @param host is the object providing I/O related operations. + * @param fileNames are the file names that belong to the same project. + * @param globalCachePath is used to get the safe list file path and as cache path if the project root path isn't specified. + * @param projectRootPath is the path to the project root directory. This is used for the local typings cache. + * @param typingOptions are used for customizing the typing inference process. + * @param compilerOptions are used as a source of typing inference. + */ + export function discoverTypings( + host: TypingResolutionHost, + fileNames: string[], + globalCachePath: Path, + projectRootPath: Path, + typingOptions: TypingOptions, + compilerOptions: CompilerOptions) + : { cachedTypingPaths: string[], newTypingNames: string[], filesToWatch: string[] } { + + // A typing name to typing file path mapping + const inferredTypings: Map = {}; + + if (!isTypingEnabled(typingOptions)) { + return { cachedTypingPaths: [], newTypingNames: [], filesToWatch: [] }; + } + + const cachePath = projectRootPath ? projectRootPath : globalCachePath; + // Only infer typings for .js and .jsx files + fileNames = fileNames + .map(ts.normalizePath) + .filter(f => scriptKindIs(f, /*LanguageServiceHost*/ undefined, ScriptKind.JS, ScriptKind.JSX)); + + const safeListFilePath = ts.combinePaths(globalCachePath, "safeList.json"); + if (!safeList && host.fileExists(safeListFilePath)) { + safeList = tryParseJson(safeListFilePath, host); + } + + const filesToWatch: string[] = []; + // Directories to search for package.json, bower.json and other typing information + let searchDirs: string[] = []; + let exclude: string[] = []; + + mergeTypings(typingOptions.include); + exclude = typingOptions.exclude ? typingOptions.exclude : []; + + if (typingOptions.enableAutoDiscovery) { + const possibleSearchDirs = fileNames.map(ts.getDirectoryPath); + if (projectRootPath !== undefined) { + possibleSearchDirs.push(projectRootPath); + } + searchDirs = ts.deduplicate(possibleSearchDirs); + for (const searchDir of searchDirs) { + const packageJsonPath = ts.combinePaths(searchDir, "package.json"); + getTypingNamesFromJson(packageJsonPath, filesToWatch); + + const bowerJsonPath = ts.combinePaths(searchDir, "bower.json"); + getTypingNamesFromJson(bowerJsonPath, filesToWatch); + + const nodeModulesPath = ts.combinePaths(searchDir, "node_modules"); + getTypingNamesFromNodeModuleFolder(nodeModulesPath, filesToWatch); + } + + getTypingNamesFromSourceFileNames(fileNames); + getTypingNamesFromCompilerOptions(compilerOptions); + } + + const typingsPath = ts.combinePaths(cachePath, "typings"); + const tsdJsonPath = ts.combinePaths(cachePath, "tsd.json"); + const tsdJsonDict = tryParseJson(tsdJsonPath, host); + if (tsdJsonDict) { + for (const notFoundTypingName of notFoundTypingNames) { + if (inferredTypings.hasOwnProperty(notFoundTypingName) && !inferredTypings[notFoundTypingName]) { + delete inferredTypings[notFoundTypingName]; + } + } + + // The "installed" property in the tsd.json serves as a registry of installed typings. Each item + // of this object has a key of the relative file path, and a value that contains the corresponding + // commit hash. + if (hasProperty(tsdJsonDict, "installed")) { + for (const cachedTypingPath in tsdJsonDict.installed) { + // Assuming the cachedTypingPath has the format of "[package name]/[file name]" + const cachedTypingName = cachedTypingPath.substr(0, cachedTypingPath.indexOf("/")); + // If the inferred[cachedTypingName] is already not null, which means we found a corresponding + // d.ts file that coming with the package. That one should take higher priority. + if (hasProperty(inferredTypings, cachedTypingName) && !inferredTypings[cachedTypingName]) { + inferredTypings[cachedTypingName] = ts.combinePaths(typingsPath, cachedTypingPath); + } + } + } + } + + // Remove typings that the user has added to the exclude list + for (const excludeTypingName of exclude) { + delete inferredTypings[excludeTypingName]; + } + + const newTypingNames: string[] = []; + const cachedTypingPaths: string[] = []; + for (const typing in inferredTypings) { + if (inferredTypings[typing] !== undefined) { + cachedTypingPaths.push(inferredTypings[typing]); + } + else { + newTypingNames.push(typing); + } + } + return { cachedTypingPaths, newTypingNames, filesToWatch }; + + /** + * Merge a given list of typingNames to the inferredTypings map + */ + function mergeTypings(typingNames: string[]) { + if (!typingNames) { + return; + } + + for (const typing of typingNames) { + if (!inferredTypings.hasOwnProperty(typing)) { + inferredTypings[typing] = undefined; + } + } + } + + /** + * Get the typing info from common package manager json files like package.json or bower.json + */ + function getTypingNamesFromJson(jsonPath: string, filesToWatch: string[]) { + const jsonDict = tryParseJson(jsonPath, host); + if (jsonDict) { + filesToWatch.push(jsonPath); + if (jsonDict.hasOwnProperty("dependencies")) { + mergeTypings(Object.keys(jsonDict.dependencies)); + } + } + } + + /** + * Infer typing names from given file names. For example, the file name "jquery-min.2.3.4.js" + * should be inferred to the 'jquery' typing name; and "angular-route.1.2.3.js" should be inferred + * to the 'angular-route' typing name. + * @param fileNames are the names for source files in the project + */ + function getTypingNamesFromSourceFileNames(fileNames: string[]) { + const jsFileNames = fileNames.filter(hasJavaScriptFileExtension); + const inferredTypingNames = jsFileNames.map(f => ts.removeFileExtension(ts.getBaseFileName(f.toLowerCase()))); + const cleanedTypingNames = inferredTypingNames.map(f => f.replace(/((?:\.|-)min(?=\.|$))|((?:-|\.)\d+)/g, "")); + safeList === undefined ? mergeTypings(cleanedTypingNames) : mergeTypings(cleanedTypingNames.filter(f => safeList.hasOwnProperty(f))); + + const jsxFileNames = fileNames.filter(f => scriptKindIs(f, /*LanguageServiceHost*/ undefined, ScriptKind.JSX)); + if (jsxFileNames.length > 0) { + mergeTypings(["react"]); + } + } + + /** + * Infer typing names from node_module folder + * @param nodeModulesPath is the path to the "node_modules" folder + */ + function getTypingNamesFromNodeModuleFolder(nodeModulesPath: string, filesToWatch: string[]) { + // Todo: add support for ModuleResolutionHost too + if (!host.directoryExists(nodeModulesPath)) { + return; + } + + const typingNames: string[] = []; + const packageJsonFiles = + host.readDirectory(nodeModulesPath, /*extension*/ undefined, /*exclude*/ undefined, /*depth*/ 2).filter(f => ts.getBaseFileName(f) === "package.json"); + for (const packageJsonFile of packageJsonFiles) { + const packageJsonDict = tryParseJson(packageJsonFile, host); + if (!packageJsonDict) { continue; } + + filesToWatch.push(packageJsonFile); + + // npm 3 has the package.json contains a "_requiredBy" field + // we should include all the top level module names for npm 2, and only module names whose + // "_requiredBy" field starts with "#" or equals "/" for npm 3. + if (packageJsonDict._requiredBy && + packageJsonDict._requiredBy.filter((r: string) => r[0] === "#" || r === "/").length === 0) { + continue; + } + + // If the package has its own d.ts typings, those will take precedence. Otherwise the package name will be used + // to download d.ts files from DefinitelyTyped + const packageName = packageJsonDict["name"]; + if (packageJsonDict.hasOwnProperty("typings")) { + const absPath = ts.getNormalizedAbsolutePath(packageJsonDict.typings, ts.getDirectoryPath(packageJsonFile)); + inferredTypings[packageName] = absPath; + } + else { + typingNames.push(packageName); + } + } + mergeTypings(typingNames); + } + + function getTypingNamesFromCompilerOptions(options: CompilerOptions) { + const typingNames: string[] = []; + if (!options) { + return; + } + + if (options.jsx === JsxEmit.React) { + typingNames.push("react"); + } + if (options.moduleResolution === ModuleResolutionKind.NodeJs) { + typingNames.push("node"); + } + mergeTypings(typingNames); + } + } + + /** + * Keep a list of typings names that we know cannot be obtained at the moment (could be because + * of network issues or because the package doesn't hava a d.ts file in DefinitelyTyped), so + * that we won't try again next time within this session. + * @param newTypingNames The list of new typings that the host attempted to acquire + * @param cachePath The path to the tsd.json cache + * @param host The object providing I/O related operations. + */ + export function updateNotFoundTypingNames(newTypingNames: string[], cachePath: string, host: TypingResolutionHost): void { + const tsdJsonPath = ts.combinePaths(cachePath, "tsd.json"); + const cacheTsdJsonDict = tryParseJson(tsdJsonPath, host); + if (cacheTsdJsonDict) { + const installedTypingFiles = hasProperty(cacheTsdJsonDict, "installed") + ? Object.keys(cacheTsdJsonDict.installed) + : []; + const newMissingTypingNames = + ts.filter(newTypingNames, name => notFoundTypingNames.indexOf(name) < 0 && !isInstalled(name, installedTypingFiles)); + for (const newMissingTypingName of newMissingTypingNames) { + notFoundTypingNames.push(newMissingTypingName); + } + } + } + + function isInstalled(typing: string, installedKeys: string[]) { + const typingPrefix = typing + "/"; + for (const key of installedKeys) { + if (key.indexOf(typingPrefix) === 0) { + return true; + } + } + return false; + } +} diff --git a/src/services/services.ts b/src/services/services.ts index 38daea8d608..9cba240a19b 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -7,6 +7,7 @@ /// /// /// +/// /// /// @@ -1751,14 +1752,13 @@ namespace ts { private createEntry(fileName: string, path: Path) { let entry: HostFileInformation; - const scriptKind = this.host.getScriptKind ? this.host.getScriptKind(fileName) : ScriptKind.Unknown; const scriptSnapshot = this.host.getScriptSnapshot(fileName); if (scriptSnapshot) { entry = { hostFileName: fileName, version: this.host.getScriptVersion(fileName), scriptSnapshot: scriptSnapshot, - scriptKind: scriptKind ? scriptKind : getScriptKindFromFileName(fileName) + scriptKind: getScriptKind(fileName, this.host) }; } @@ -1824,7 +1824,7 @@ namespace ts { throw new Error("Could not find file: '" + fileName + "'."); } - const scriptKind = this.host.getScriptKind ? this.host.getScriptKind(fileName) : ScriptKind.Unknown; + const scriptKind = getScriptKind(fileName, this.host); const version = this.host.getScriptVersion(fileName); let sourceFile: SourceFile; diff --git a/src/services/shims.ts b/src/services/shims.ts index f8c51feca27..add936a6a79 100644 --- a/src/services/shims.ts +++ b/src/services/shims.ts @@ -78,7 +78,7 @@ namespace ts { * @param exclude A JSON encoded string[] containing the paths to exclude * when enumerating the directory. */ - readDirectory(rootDir: string, extension: string, exclude?: string): string; + readDirectory(rootDir: string, extension: string, exclude?: string, depth?: number): string; trace(s: string): void; } @@ -232,6 +232,8 @@ namespace ts { getPreProcessedFileInfo(fileName: string, sourceText: IScriptSnapshot): string; getTSConfigFileInfo(fileName: string, sourceText: IScriptSnapshot): string; getDefaultCompilationSettings(): string; + resolveTypeDefinitions(fileNamesJson: string, globalCachePath: string, projectRootPath: string, typingOptionsJson: string, compilerOptionsJson: string): string; + updateNotFoundTypingNames(newTypingsJson: string, globalCachePath: string, projectRootPath: string): string; } function logInternalError(logger: Logger, err: Error) { @@ -422,8 +424,16 @@ namespace ts { } } - public readDirectory(rootDir: string, extension: string, exclude: string[]): string[] { - const encoded = this.shimHost.readDirectory(rootDir, extension, JSON.stringify(exclude)); + public readDirectory(rootDir: string, extension: string, exclude: string[], depth?: number): string[] { + // Wrap the API changes for 2.0 release. This try/catch + // should be removed once TypeScript 2.0 has shipped. + let encoded: string; + try { + encoded = this.shimHost.readDirectory(rootDir, extension, JSON.stringify(exclude), depth); + } + catch (e) { + encoded = this.shimHost.readDirectory(rootDir, extension, JSON.stringify(exclude)); + } return JSON.parse(encoded); } @@ -953,6 +963,7 @@ namespace ts { if (result.error) { return { options: {}, + typingOptions: {}, files: [], errors: [realizeDiagnostic(result.error, "\r\n")] }; @@ -963,6 +974,7 @@ namespace ts { return { options: configFile.options, + typingOptions: configFile.typingOptions, files: configFile.fileNames, errors: realizeDiagnostics(configFile.errors, "\r\n") }; @@ -975,6 +987,35 @@ namespace ts { () => getDefaultCompilerOptions() ); } + + public resolveTypeDefinitions(fileNamesJson: string, globalCachePath: string, projectRootPath: string, typingOptionsJson: string, compilerOptionsJson: string): string { + const getCanonicalFileName = createGetCanonicalFileName(/*useCaseSensitivefileNames:*/ false); + return this.forwardJSONCall("resolveTypeDefinitions()", () => { + const cachePath = projectRootPath ? projectRootPath : globalCachePath; + const typingOptions = JSON.parse(typingOptionsJson); + // Convert the include and exclude lists from a semi-colon delimited string to a string array + typingOptions.include = typingOptions.include ? typingOptions.include.toString().split(";") : []; + typingOptions.exclude = typingOptions.exclude ? typingOptions.exclude.toString().split(";") : []; + + const compilerOptions = JSON.parse(compilerOptionsJson); + const fileNames: string[] = JSON.parse(fileNamesJson); + return ts.JsTyping.discoverTypings( + this.host, + fileNames, + toPath(globalCachePath, globalCachePath, getCanonicalFileName), + toPath(cachePath, cachePath, getCanonicalFileName), + typingOptions, + compilerOptions); + }); + } + + public updateNotFoundTypingNames(newTypingsJson: string, globalCachePath: string, projectRootPath: string): string { + return this.forwardJSONCall("updateNotFoundTypingNames()", () => { + const newTypingNames: string[] = JSON.parse(newTypingsJson); + const cachePath = projectRootPath ? projectRootPath : globalCachePath; + ts.JsTyping.updateNotFoundTypingNames(newTypingNames, cachePath, this.host); + }); + } } export class TypeScriptServicesFactory implements ShimFactory { diff --git a/src/services/tsconfig.json b/src/services/tsconfig.json index 001071ed88d..6aa7e61391b 100644 --- a/src/services/tsconfig.json +++ b/src/services/tsconfig.json @@ -29,6 +29,7 @@ "shims.ts", "signatureHelp.ts", "utilities.ts", + "jsTyping.ts", "formatting/formatting.ts", "formatting/formattingContext.ts", "formatting/formattingRequestKind.ts", diff --git a/src/services/utilities.ts b/src/services/utilities.ts index afdc85fffd8..e423d870ca4 100644 --- a/src/services/utilities.ts +++ b/src/services/utilities.ts @@ -837,4 +837,19 @@ namespace ts { }; return name; } + + export function scriptKindIs(fileName: string, host: LanguageServiceHost, ...scriptKinds: ScriptKind[]): boolean { + const scriptKind = getScriptKind(fileName, host); + return forEach(scriptKinds, k => k === scriptKind); + } + + export function getScriptKind(fileName: string, host?: LanguageServiceHost): ScriptKind { + // First check to see if the script kind can be determined from the file name + var scriptKind = getScriptKindFromFileName(fileName); + if (scriptKind === ScriptKind.Unknown && host && host.getScriptKind) { + // Next check to see if the host can resolve the script kind + scriptKind = host.getScriptKind(fileName); + } + return ensureScriptKind(fileName, scriptKind); + } } \ No newline at end of file From 0aaedc5df43624a88962ee482ea5e1c017df31c3 Mon Sep 17 00:00:00 2001 From: Jason Ramsay Date: Sun, 21 Feb 2016 21:57:37 -0800 Subject: [PATCH 02/15] Fixing lint issues caught by Travis CI build (Rules appear to be more strict - this was not caught on a local lint run) --- src/server/protocol.d.ts | 2 +- src/services/jsTyping.ts | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/server/protocol.d.ts b/src/server/protocol.d.ts index 3a669753323..c50f211a21f 100644 --- a/src/server/protocol.d.ts +++ b/src/server/protocol.d.ts @@ -469,7 +469,7 @@ declare namespace ts.server.protocol { placeOpenBraceOnNewLineForControlBlocks?: boolean; /** Index operator */ - [key: string] : string | number | boolean; + [key: string]: string | number | boolean; } /** diff --git a/src/services/jsTyping.ts b/src/services/jsTyping.ts index bf3c6b6cc9a..e4b221bf051 100644 --- a/src/services/jsTyping.ts +++ b/src/services/jsTyping.ts @@ -55,8 +55,8 @@ namespace ts.JsTyping { globalCachePath: Path, projectRootPath: Path, typingOptions: TypingOptions, - compilerOptions: CompilerOptions) - : { cachedTypingPaths: string[], newTypingNames: string[], filesToWatch: string[] } { + compilerOptions: CompilerOptions): + { cachedTypingPaths: string[], newTypingNames: string[], filesToWatch: string[] } { // A typing name to typing file path mapping const inferredTypings: Map = {}; From 5b06edbc5428fbd8fd711ff53ded87aa6dc48f86 Mon Sep 17 00:00:00 2001 From: Jason Ramsay Date: Mon, 22 Feb 2016 19:00:06 -0800 Subject: [PATCH 03/15] Addressing CR comments - Adding check to ensure TypingOptions 'include' and 'exclude' arrays are composed of strings - Allow leading whitespace when removing comments from json --- src/compiler/commandLineParser.ts | 51 ++++++++++++++++--------------- src/services/jsTyping.ts | 6 ++-- 2 files changed, 30 insertions(+), 27 deletions(-) diff --git a/src/compiler/commandLineParser.ts b/src/compiler/commandLineParser.ts index af7b3a747cb..254fc434f64 100644 --- a/src/compiler/commandLineParser.ts +++ b/src/compiler/commandLineParser.ts @@ -616,10 +616,10 @@ namespace ts { } } else if (id === "include") { - options.include = isArray(jsonTypingOptions[id]) ? jsonTypingOptions[id] : []; + options.include = ConvertJsonOptionToStringArray(id, jsonTypingOptions[id], errors); } else if (id === "exclude") { - options.exclude = isArray(jsonTypingOptions[id]) ? jsonTypingOptions[id] : []; + options.exclude = ConvertJsonOptionToStringArray(id, jsonTypingOptions[id], errors); } else { errors.push(createCompilerDiagnostic(Diagnostics.Unknown_typing_option_0, id)); @@ -668,28 +668,7 @@ namespace ts { break; case "object": // "object" options with 'isFilePath' = true expected to be string arrays - let paths: string[] = []; - let invalidOptionType = false; - if (!isArray(value)) { - invalidOptionType = true; - } - else { - for (const element of value) { - if (typeof element === "string") { - paths.push(normalizePath(combinePaths(basePath, element))); - } - else { - invalidOptionType = true; - break; - } - } - } - if (invalidOptionType) { - errors.push(createCompilerDiagnostic(Diagnostics.Option_0_should_have_array_of_strings_as_a_value, opt.name)); - } - else { - value = paths; - } + value = ConvertJsonOptionToStringArray(opt.name, value, errors, (element) => normalizePath(combinePaths(basePath, element))); break; } if (value === "") { @@ -709,4 +688,28 @@ namespace ts { return { options, errors }; } + + function ConvertJsonOptionToStringArray(optionName: string, optionJson: any, errors: Diagnostic[], func?: (element: string) => string): string[] { + let items: string[] = []; + let invalidOptionType = false; + if (!isArray(optionJson)) { + invalidOptionType = true; + } + else { + for (const element of optionJson) { + if (typeof element === "string") { + const item = func ? func(element) : element; + items.push(item); + } + else { + invalidOptionType = true; + break; + } + } + } + if (invalidOptionType) { + errors.push(createCompilerDiagnostic(Diagnostics.Option_0_should_have_array_of_strings_as_a_value, optionName)); + } + return items; + } } diff --git a/src/services/jsTyping.ts b/src/services/jsTyping.ts index e4b221bf051..bfb62e396c6 100644 --- a/src/services/jsTyping.ts +++ b/src/services/jsTyping.ts @@ -22,7 +22,7 @@ namespace ts.JsTyping { if (host.fileExists(jsonPath)) { try { // Strip out single-line comments - const contents = host.readFile(jsonPath).replace(/^\/\/(.*)$/gm, ""); + const contents = host.readFile(jsonPath).replace(/^\s*\/\/(.*)$/gm, ""); return JSON.parse(contents); } catch (e) { } @@ -65,7 +65,7 @@ namespace ts.JsTyping { return { cachedTypingPaths: [], newTypingNames: [], filesToWatch: [] }; } - const cachePath = projectRootPath ? projectRootPath : globalCachePath; + const cachePath = projectRootPath || globalCachePath; // Only infer typings for .js and .jsx files fileNames = fileNames .map(ts.normalizePath) @@ -82,7 +82,7 @@ namespace ts.JsTyping { let exclude: string[] = []; mergeTypings(typingOptions.include); - exclude = typingOptions.exclude ? typingOptions.exclude : []; + exclude = typingOptions.exclude || []; if (typingOptions.enableAutoDiscovery) { const possibleSearchDirs = fileNames.map(ts.getDirectoryPath); From 71bfefccb97b5e6d7e77b14fa3385cfd682a2945 Mon Sep 17 00:00:00 2001 From: Jason Ramsay Date: Mon, 22 Feb 2016 19:33:45 -0800 Subject: [PATCH 04/15] Switch let -> const from lint validation --- src/compiler/commandLineParser.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/compiler/commandLineParser.ts b/src/compiler/commandLineParser.ts index 254fc434f64..f180bc83ad3 100644 --- a/src/compiler/commandLineParser.ts +++ b/src/compiler/commandLineParser.ts @@ -690,7 +690,7 @@ namespace ts { } function ConvertJsonOptionToStringArray(optionName: string, optionJson: any, errors: Diagnostic[], func?: (element: string) => string): string[] { - let items: string[] = []; + const items: string[] = []; let invalidOptionType = false; if (!isArray(optionJson)) { invalidOptionType = true; From 20511f8be1ced319fc815c7cc9e8a2c4d1771293 Mon Sep 17 00:00:00 2001 From: Jason Ramsay Date: Tue, 23 Feb 2016 10:31:56 -0800 Subject: [PATCH 05/15] Adding devDependencies to the list of typings to merge --- src/services/jsTyping.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/services/jsTyping.ts b/src/services/jsTyping.ts index bfb62e396c6..d561734661b 100644 --- a/src/services/jsTyping.ts +++ b/src/services/jsTyping.ts @@ -173,6 +173,9 @@ namespace ts.JsTyping { if (jsonDict.hasOwnProperty("dependencies")) { mergeTypings(Object.keys(jsonDict.dependencies)); } + if (jsonDict.hasOwnProperty("devDependencies")) { + mergeTypings(Object.keys(jsonDict.devDependencies)); + } } } From 18883f9d32ad64fa3cdebcb6c85d15ed10755b3a Mon Sep 17 00:00:00 2001 From: Jason Ramsay Date: Tue, 23 Feb 2016 13:30:24 -0800 Subject: [PATCH 06/15] Using removeComments from commandLineParser. This is more robust as it removes both single and multiline comments --- src/compiler/commandLineParser.ts | 2 +- src/services/jsTyping.ts | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/compiler/commandLineParser.ts b/src/compiler/commandLineParser.ts index f180bc83ad3..947ca1ca00c 100644 --- a/src/compiler/commandLineParser.ts +++ b/src/compiler/commandLineParser.ts @@ -503,7 +503,7 @@ namespace ts { * * This method replace comment content by whitespace rather than completely remove them to keep positions in json parsing error reporting accurate. */ - function removeComments(jsonText: string): string { + export function removeComments(jsonText: string): string { let output = ""; const scanner = createScanner(ScriptTarget.ES5, /* skipTrivia */ false, LanguageVariant.Standard, jsonText); let token: SyntaxKind; diff --git a/src/services/jsTyping.ts b/src/services/jsTyping.ts index d561734661b..251995a2991 100644 --- a/src/services/jsTyping.ts +++ b/src/services/jsTyping.ts @@ -21,8 +21,7 @@ namespace ts.JsTyping { function tryParseJson(jsonPath: string, host: TypingResolutionHost): any { if (host.fileExists(jsonPath)) { try { - // Strip out single-line comments - const contents = host.readFile(jsonPath).replace(/^\s*\/\/(.*)$/gm, ""); + const contents = removeComments(host.readFile(jsonPath)); return JSON.parse(contents); } catch (e) { } From 70ca4bd8a85279cf72b737f99303208c3b4739f9 Mon Sep 17 00:00:00 2001 From: Jason Ramsay Date: Thu, 25 Feb 2016 12:32:43 -0800 Subject: [PATCH 07/15] - renaming resolveTypeDefinitions to discoverTypings for consistency with jsTypings - simplifying typingOptions parsing after associated managed host changes --- src/services/shims.ts | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/src/services/shims.ts b/src/services/shims.ts index add936a6a79..47a64ab58f6 100644 --- a/src/services/shims.ts +++ b/src/services/shims.ts @@ -232,7 +232,7 @@ namespace ts { getPreProcessedFileInfo(fileName: string, sourceText: IScriptSnapshot): string; getTSConfigFileInfo(fileName: string, sourceText: IScriptSnapshot): string; getDefaultCompilationSettings(): string; - resolveTypeDefinitions(fileNamesJson: string, globalCachePath: string, projectRootPath: string, typingOptionsJson: string, compilerOptionsJson: string): string; + discoverTypings(fileNamesJson: string, globalCachePath: string, projectRootPath: string, typingOptionsJson: string, compilerOptionsJson: string): string; updateNotFoundTypingNames(newTypingsJson: string, globalCachePath: string, projectRootPath: string): string; } @@ -988,14 +988,11 @@ namespace ts { ); } - public resolveTypeDefinitions(fileNamesJson: string, globalCachePath: string, projectRootPath: string, typingOptionsJson: string, compilerOptionsJson: string): string { + public discoverTypings(fileNamesJson: string, globalCachePath: string, projectRootPath: string, typingOptionsJson: string, compilerOptionsJson: string): string { const getCanonicalFileName = createGetCanonicalFileName(/*useCaseSensitivefileNames:*/ false); - return this.forwardJSONCall("resolveTypeDefinitions()", () => { + return this.forwardJSONCall("discoverTypings()", () => { const cachePath = projectRootPath ? projectRootPath : globalCachePath; const typingOptions = JSON.parse(typingOptionsJson); - // Convert the include and exclude lists from a semi-colon delimited string to a string array - typingOptions.include = typingOptions.include ? typingOptions.include.toString().split(";") : []; - typingOptions.exclude = typingOptions.exclude ? typingOptions.exclude.toString().split(";") : []; const compilerOptions = JSON.parse(compilerOptionsJson); const fileNames: string[] = JSON.parse(fileNamesJson); From 39a51d3731e5868c09c23358ebb02d3d140f627d Mon Sep 17 00:00:00 2001 From: zhengbli Date: Fri, 26 Feb 2016 14:15:07 -0800 Subject: [PATCH 08/15] Unify the use of "filter", "map" and "Object.keys" functions --- src/compiler/core.ts | 8 +++++++ src/services/jsTyping.ts | 45 ++++++++++++++++++++++------------------ 2 files changed, 33 insertions(+), 20 deletions(-) diff --git a/src/compiler/core.ts b/src/compiler/core.ts index 59274201155..93286db7e46 100644 --- a/src/compiler/core.ts +++ b/src/compiler/core.ts @@ -278,6 +278,14 @@ namespace ts { return hasOwnProperty.call(map, key); } + export function getKeys(map: Map): string[] { + const keys: string[] = []; + for (const key in map) { + keys.push(key); + } + return keys; + } + export function getProperty(map: Map, key: string): T { return hasOwnProperty.call(map, key) ? map[key] : undefined; } diff --git a/src/services/jsTyping.ts b/src/services/jsTyping.ts index 251995a2991..b3955e89148 100644 --- a/src/services/jsTyping.ts +++ b/src/services/jsTyping.ts @@ -66,9 +66,7 @@ namespace ts.JsTyping { const cachePath = projectRootPath || globalCachePath; // Only infer typings for .js and .jsx files - fileNames = fileNames - .map(ts.normalizePath) - .filter(f => scriptKindIs(f, /*LanguageServiceHost*/ undefined, ScriptKind.JS, ScriptKind.JSX)); + fileNames = filter(map(fileNames, ts.normalizePath), f => scriptKindIs(f, /*LanguageServiceHost*/ undefined, ScriptKind.JS, ScriptKind.JSX)); const safeListFilePath = ts.combinePaths(globalCachePath, "safeList.json"); if (!safeList && host.fileExists(safeListFilePath)) { @@ -84,7 +82,7 @@ namespace ts.JsTyping { exclude = typingOptions.exclude || []; if (typingOptions.enableAutoDiscovery) { - const possibleSearchDirs = fileNames.map(ts.getDirectoryPath); + const possibleSearchDirs = map(fileNames, ts.getDirectoryPath); if (projectRootPath !== undefined) { possibleSearchDirs.push(projectRootPath); } @@ -109,7 +107,7 @@ namespace ts.JsTyping { const tsdJsonDict = tryParseJson(tsdJsonPath, host); if (tsdJsonDict) { for (const notFoundTypingName of notFoundTypingNames) { - if (inferredTypings.hasOwnProperty(notFoundTypingName) && !inferredTypings[notFoundTypingName]) { + if (hasProperty(inferredTypings, notFoundTypingName) && !inferredTypings[notFoundTypingName]) { delete inferredTypings[notFoundTypingName]; } } @@ -156,7 +154,7 @@ namespace ts.JsTyping { } for (const typing of typingNames) { - if (!inferredTypings.hasOwnProperty(typing)) { + if (!hasProperty(inferredTypings, typing)) { inferredTypings[typing] = undefined; } } @@ -169,11 +167,11 @@ namespace ts.JsTyping { const jsonDict = tryParseJson(jsonPath, host); if (jsonDict) { filesToWatch.push(jsonPath); - if (jsonDict.hasOwnProperty("dependencies")) { - mergeTypings(Object.keys(jsonDict.dependencies)); + if (hasProperty(jsonDict, "dependencies")) { + mergeTypings(getKeys(jsonDict.dependencies)); } - if (jsonDict.hasOwnProperty("devDependencies")) { - mergeTypings(Object.keys(jsonDict.devDependencies)); + if (hasProperty(jsonDict, "devDependencies")) { + mergeTypings(getKeys(jsonDict.devDependencies)); } } } @@ -185,12 +183,17 @@ namespace ts.JsTyping { * @param fileNames are the names for source files in the project */ function getTypingNamesFromSourceFileNames(fileNames: string[]) { - const jsFileNames = fileNames.filter(hasJavaScriptFileExtension); - const inferredTypingNames = jsFileNames.map(f => ts.removeFileExtension(ts.getBaseFileName(f.toLowerCase()))); - const cleanedTypingNames = inferredTypingNames.map(f => f.replace(/((?:\.|-)min(?=\.|$))|((?:-|\.)\d+)/g, "")); - safeList === undefined ? mergeTypings(cleanedTypingNames) : mergeTypings(cleanedTypingNames.filter(f => safeList.hasOwnProperty(f))); + const jsFileNames = filter(fileNames, hasJavaScriptFileExtension); + const inferredTypingNames = map(jsFileNames, f => ts.removeFileExtension(ts.getBaseFileName(f.toLowerCase()))); + const cleanedTypingNames = map(inferredTypingNames, f => f.replace(/((?:\.|-)min(?=\.|$))|((?:-|\.)\d+)/g, "")); + if (safeList === undefined) { + mergeTypings(cleanedTypingNames); + } + else { + mergeTypings(filter(cleanedTypingNames, f => hasProperty(safeList, f))); + } - const jsxFileNames = fileNames.filter(f => scriptKindIs(f, /*LanguageServiceHost*/ undefined, ScriptKind.JSX)); + const jsxFileNames = filter(fileNames, f => scriptKindIs(f, /*LanguageServiceHost*/ undefined, ScriptKind.JSX)); if (jsxFileNames.length > 0) { mergeTypings(["react"]); } @@ -208,7 +211,9 @@ namespace ts.JsTyping { const typingNames: string[] = []; const packageJsonFiles = - host.readDirectory(nodeModulesPath, /*extension*/ undefined, /*exclude*/ undefined, /*depth*/ 2).filter(f => ts.getBaseFileName(f) === "package.json"); + filter( + host.readDirectory(nodeModulesPath, /*extension*/ undefined, /*exclude*/ undefined, /*depth*/ 2), + f => ts.getBaseFileName(f) === "package.json"); for (const packageJsonFile of packageJsonFiles) { const packageJsonDict = tryParseJson(packageJsonFile, host); if (!packageJsonDict) { continue; } @@ -219,14 +224,14 @@ namespace ts.JsTyping { // we should include all the top level module names for npm 2, and only module names whose // "_requiredBy" field starts with "#" or equals "/" for npm 3. if (packageJsonDict._requiredBy && - packageJsonDict._requiredBy.filter((r: string) => r[0] === "#" || r === "/").length === 0) { + filter(packageJsonDict._requiredBy, (r: string) => r[0] === "#" || r === "/").length === 0) { continue; } // If the package has its own d.ts typings, those will take precedence. Otherwise the package name will be used // to download d.ts files from DefinitelyTyped const packageName = packageJsonDict["name"]; - if (packageJsonDict.hasOwnProperty("typings")) { + if (hasProperty(packageJsonDict, "typings")) { const absPath = ts.getNormalizedAbsolutePath(packageJsonDict.typings, ts.getDirectoryPath(packageJsonFile)); inferredTypings[packageName] = absPath; } @@ -266,10 +271,10 @@ namespace ts.JsTyping { const cacheTsdJsonDict = tryParseJson(tsdJsonPath, host); if (cacheTsdJsonDict) { const installedTypingFiles = hasProperty(cacheTsdJsonDict, "installed") - ? Object.keys(cacheTsdJsonDict.installed) + ? getKeys(cacheTsdJsonDict.installed) : []; const newMissingTypingNames = - ts.filter(newTypingNames, name => notFoundTypingNames.indexOf(name) < 0 && !isInstalled(name, installedTypingFiles)); + filter(newTypingNames, name => notFoundTypingNames.indexOf(name) < 0 && !isInstalled(name, installedTypingFiles)); for (const newMissingTypingName of newMissingTypingNames) { notFoundTypingNames.push(newMissingTypingName); } From 5981d8e60c6ca81da815742d3ae646453b8c68da Mon Sep 17 00:00:00 2001 From: zhengbli Date: Fri, 26 Feb 2016 14:27:37 -0800 Subject: [PATCH 09/15] CR feedback --- src/compiler/commandLineParser.ts | 8 ++++---- src/services/jsTyping.ts | 12 +++--------- 2 files changed, 7 insertions(+), 13 deletions(-) diff --git a/src/compiler/commandLineParser.ts b/src/compiler/commandLineParser.ts index 947ca1ca00c..403345c9b04 100644 --- a/src/compiler/commandLineParser.ts +++ b/src/compiler/commandLineParser.ts @@ -616,10 +616,10 @@ namespace ts { } } else if (id === "include") { - options.include = ConvertJsonOptionToStringArray(id, jsonTypingOptions[id], errors); + options.include = convertJsonOptionToStringArray(id, jsonTypingOptions[id], errors); } else if (id === "exclude") { - options.exclude = ConvertJsonOptionToStringArray(id, jsonTypingOptions[id], errors); + options.exclude = convertJsonOptionToStringArray(id, jsonTypingOptions[id], errors); } else { errors.push(createCompilerDiagnostic(Diagnostics.Unknown_typing_option_0, id)); @@ -668,7 +668,7 @@ namespace ts { break; case "object": // "object" options with 'isFilePath' = true expected to be string arrays - value = ConvertJsonOptionToStringArray(opt.name, value, errors, (element) => normalizePath(combinePaths(basePath, element))); + value = convertJsonOptionToStringArray(opt.name, value, errors, (element) => normalizePath(combinePaths(basePath, element))); break; } if (value === "") { @@ -689,7 +689,7 @@ namespace ts { return { options, errors }; } - function ConvertJsonOptionToStringArray(optionName: string, optionJson: any, errors: Diagnostic[], func?: (element: string) => string): string[] { + function convertJsonOptionToStringArray(optionName: string, optionJson: any, errors: Diagnostic[], func?: (element: string) => string): string[] { const items: string[] = []; let invalidOptionType = false; if (!isArray(optionJson)) { diff --git a/src/services/jsTyping.ts b/src/services/jsTyping.ts index b3955e89148..59918ab2445 100644 --- a/src/services/jsTyping.ts +++ b/src/services/jsTyping.ts @@ -193,8 +193,8 @@ namespace ts.JsTyping { mergeTypings(filter(cleanedTypingNames, f => hasProperty(safeList, f))); } - const jsxFileNames = filter(fileNames, f => scriptKindIs(f, /*LanguageServiceHost*/ undefined, ScriptKind.JSX)); - if (jsxFileNames.length > 0) { + const hasJsxFile = forEach(fileNames, f => scriptKindIs(f, /*LanguageServiceHost*/ undefined, ScriptKind.JSX)); + if (hasJsxFile) { mergeTypings(["react"]); } } @@ -214,6 +214,7 @@ namespace ts.JsTyping { filter( host.readDirectory(nodeModulesPath, /*extension*/ undefined, /*exclude*/ undefined, /*depth*/ 2), f => ts.getBaseFileName(f) === "package.json"); + for (const packageJsonFile of packageJsonFiles) { const packageJsonDict = tryParseJson(packageJsonFile, host); if (!packageJsonDict) { continue; } @@ -247,13 +248,6 @@ namespace ts.JsTyping { if (!options) { return; } - - if (options.jsx === JsxEmit.React) { - typingNames.push("react"); - } - if (options.moduleResolution === ModuleResolutionKind.NodeJs) { - typingNames.push("node"); - } mergeTypings(typingNames); } } From f76ef47174e84adcd9c5e379685bc258d8d86222 Mon Sep 17 00:00:00 2001 From: Jason Ramsay Date: Fri, 26 Feb 2016 15:33:34 -0800 Subject: [PATCH 10/15] Adding optionalDependencies and peerDependencies to the list typings to merge in if present. --- src/services/jsTyping.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/services/jsTyping.ts b/src/services/jsTyping.ts index 59918ab2445..a3268880905 100644 --- a/src/services/jsTyping.ts +++ b/src/services/jsTyping.ts @@ -173,6 +173,12 @@ namespace ts.JsTyping { if (hasProperty(jsonDict, "devDependencies")) { mergeTypings(getKeys(jsonDict.devDependencies)); } + if (hasProperty(jsonDict, "optionalDependencies")) { + mergeTypings(getKeys(jsonDict.optionalDependencies)); + } + if (hasProperty(jsonDict, "peerDependencies")) { + mergeTypings(getKeys(jsonDict.peerDependencies)); + } } } From 0346a9889c090d04570db037a3e9d9b714d6c38d Mon Sep 17 00:00:00 2001 From: Jason Ramsay Date: Mon, 29 Feb 2016 08:14:00 -0800 Subject: [PATCH 11/15] - Removing ts. from jsTyping.js - Adding ".json" file extension filter when retrieving json files from host and removoing filter - simplify isTypingEnabled check --- src/services/jsTyping.ts | 54 +++++++++++++++------------------------- 1 file changed, 20 insertions(+), 34 deletions(-) diff --git a/src/services/jsTyping.ts b/src/services/jsTyping.ts index a3268880905..352b6be77e2 100644 --- a/src/services/jsTyping.ts +++ b/src/services/jsTyping.ts @@ -29,17 +29,6 @@ namespace ts.JsTyping { return undefined; } - function isTypingEnabled(options: TypingOptions): boolean { - if (options) { - if (options.enableAutoDiscovery || - (options.include && options.include.length > 0) || - (options.exclude && options.exclude.length > 0)) { - return true; - } - } - return false; - } - /** * @param host is the object providing I/O related operations. * @param fileNames are the file names that belong to the same project. @@ -60,15 +49,15 @@ namespace ts.JsTyping { // A typing name to typing file path mapping const inferredTypings: Map = {}; - if (!isTypingEnabled(typingOptions)) { + if (!typingOptions || !typingOptions.enableAutoDiscovery) { return { cachedTypingPaths: [], newTypingNames: [], filesToWatch: [] }; } const cachePath = projectRootPath || globalCachePath; // Only infer typings for .js and .jsx files - fileNames = filter(map(fileNames, ts.normalizePath), f => scriptKindIs(f, /*LanguageServiceHost*/ undefined, ScriptKind.JS, ScriptKind.JSX)); + fileNames = filter(map(fileNames, normalizePath), f => scriptKindIs(f, /*LanguageServiceHost*/ undefined, ScriptKind.JS, ScriptKind.JSX)); - const safeListFilePath = ts.combinePaths(globalCachePath, "safeList.json"); + const safeListFilePath = combinePaths(globalCachePath, "safeList.json"); if (!safeList && host.fileExists(safeListFilePath)) { safeList = tryParseJson(safeListFilePath, host); } @@ -82,19 +71,19 @@ namespace ts.JsTyping { exclude = typingOptions.exclude || []; if (typingOptions.enableAutoDiscovery) { - const possibleSearchDirs = map(fileNames, ts.getDirectoryPath); + const possibleSearchDirs = map(fileNames, getDirectoryPath); if (projectRootPath !== undefined) { possibleSearchDirs.push(projectRootPath); } - searchDirs = ts.deduplicate(possibleSearchDirs); + searchDirs = deduplicate(possibleSearchDirs); for (const searchDir of searchDirs) { - const packageJsonPath = ts.combinePaths(searchDir, "package.json"); + const packageJsonPath = combinePaths(searchDir, "package.json"); getTypingNamesFromJson(packageJsonPath, filesToWatch); - const bowerJsonPath = ts.combinePaths(searchDir, "bower.json"); + const bowerJsonPath = combinePaths(searchDir, "bower.json"); getTypingNamesFromJson(bowerJsonPath, filesToWatch); - const nodeModulesPath = ts.combinePaths(searchDir, "node_modules"); + const nodeModulesPath = combinePaths(searchDir, "node_modules"); getTypingNamesFromNodeModuleFolder(nodeModulesPath, filesToWatch); } @@ -102,8 +91,8 @@ namespace ts.JsTyping { getTypingNamesFromCompilerOptions(compilerOptions); } - const typingsPath = ts.combinePaths(cachePath, "typings"); - const tsdJsonPath = ts.combinePaths(cachePath, "tsd.json"); + const typingsPath = combinePaths(cachePath, "typings"); + const tsdJsonPath = combinePaths(cachePath, "tsd.json"); const tsdJsonDict = tryParseJson(tsdJsonPath, host); if (tsdJsonDict) { for (const notFoundTypingName of notFoundTypingNames) { @@ -122,7 +111,7 @@ namespace ts.JsTyping { // If the inferred[cachedTypingName] is already not null, which means we found a corresponding // d.ts file that coming with the package. That one should take higher priority. if (hasProperty(inferredTypings, cachedTypingName) && !inferredTypings[cachedTypingName]) { - inferredTypings[cachedTypingName] = ts.combinePaths(typingsPath, cachedTypingPath); + inferredTypings[cachedTypingName] = combinePaths(typingsPath, cachedTypingPath); } } } @@ -190,7 +179,7 @@ namespace ts.JsTyping { */ function getTypingNamesFromSourceFileNames(fileNames: string[]) { const jsFileNames = filter(fileNames, hasJavaScriptFileExtension); - const inferredTypingNames = map(jsFileNames, f => ts.removeFileExtension(ts.getBaseFileName(f.toLowerCase()))); + const inferredTypingNames = map(jsFileNames, f => removeFileExtension(getBaseFileName(f.toLowerCase()))); const cleanedTypingNames = map(inferredTypingNames, f => f.replace(/((?:\.|-)min(?=\.|$))|((?:-|\.)\d+)/g, "")); if (safeList === undefined) { mergeTypings(cleanedTypingNames); @@ -216,16 +205,13 @@ namespace ts.JsTyping { } const typingNames: string[] = []; - const packageJsonFiles = - filter( - host.readDirectory(nodeModulesPath, /*extension*/ undefined, /*exclude*/ undefined, /*depth*/ 2), - f => ts.getBaseFileName(f) === "package.json"); - - for (const packageJsonFile of packageJsonFiles) { - const packageJsonDict = tryParseJson(packageJsonFile, host); + const jsonFiles = host.readDirectory(nodeModulesPath, "*.json", /*exclude*/ undefined, /*depth*/ 2); + for (const jsonFile of jsonFiles) { + if (getBaseFileName(jsonFile) !== "package.json") { continue; } + const packageJsonDict = tryParseJson(jsonFile, host); if (!packageJsonDict) { continue; } - filesToWatch.push(packageJsonFile); + filesToWatch.push(jsonFile); // npm 3 has the package.json contains a "_requiredBy" field // we should include all the top level module names for npm 2, and only module names whose @@ -239,8 +225,8 @@ namespace ts.JsTyping { // to download d.ts files from DefinitelyTyped const packageName = packageJsonDict["name"]; if (hasProperty(packageJsonDict, "typings")) { - const absPath = ts.getNormalizedAbsolutePath(packageJsonDict.typings, ts.getDirectoryPath(packageJsonFile)); - inferredTypings[packageName] = absPath; + const absolutePath = getNormalizedAbsolutePath(packageJsonDict.typings, getDirectoryPath(jsonFile)); + inferredTypings[packageName] = absolutePath; } else { typingNames.push(packageName); @@ -267,7 +253,7 @@ namespace ts.JsTyping { * @param host The object providing I/O related operations. */ export function updateNotFoundTypingNames(newTypingNames: string[], cachePath: string, host: TypingResolutionHost): void { - const tsdJsonPath = ts.combinePaths(cachePath, "tsd.json"); + const tsdJsonPath = combinePaths(cachePath, "tsd.json"); const cacheTsdJsonDict = tryParseJson(tsdJsonPath, host); if (cacheTsdJsonDict) { const installedTypingFiles = hasProperty(cacheTsdJsonDict, "installed") From b3ceea3b3d4880080b2a980e3aa4e5ec94b9b08c Mon Sep 17 00:00:00 2001 From: Jason Ramsay Date: Tue, 1 Mar 2016 11:50:27 -0800 Subject: [PATCH 12/15] - replacing TryParseJson with existing readConfig - push error for invalid enableAutoDiscovery option - adding interfaces for jsons - removing updateNotFoundTypings - node_modules normalize file names before using - adding safeListPath to discoverTypings --- src/compiler/commandLineParser.ts | 5 +- src/services/jsTyping.ts | 157 ++++++++++++------------------ src/services/shims.ts | 18 +--- 3 files changed, 70 insertions(+), 110 deletions(-) diff --git a/src/compiler/commandLineParser.ts b/src/compiler/commandLineParser.ts index 403345c9b04..226dcd750af 100644 --- a/src/compiler/commandLineParser.ts +++ b/src/compiler/commandLineParser.ts @@ -503,7 +503,7 @@ namespace ts { * * This method replace comment content by whitespace rather than completely remove them to keep positions in json parsing error reporting accurate. */ - export function removeComments(jsonText: string): string { + function removeComments(jsonText: string): string { let output = ""; const scanner = createScanner(ScriptTarget.ES5, /* skipTrivia */ false, LanguageVariant.Standard, jsonText); let token: SyntaxKind; @@ -614,6 +614,9 @@ namespace ts { if (typeof jsonTypingOptions[id] === "boolean") { options.enableAutoDiscovery = jsonTypingOptions[id]; } + else { + errors.push(createCompilerDiagnostic(Diagnostics.Unknown_typing_option_0, id)); + } } else if (id === "include") { options.include = convertJsonOptionToStringArray(id, jsonTypingOptions[id], errors); diff --git a/src/services/jsTyping.ts b/src/services/jsTyping.ts index 352b6be77e2..786a199eb43 100644 --- a/src/services/jsTyping.ts +++ b/src/services/jsTyping.ts @@ -13,35 +13,47 @@ namespace ts.JsTyping { readDirectory: (path: string, extension?: string, exclude?: string[], depth?: number) => string[]; }; + interface TsdJson { + version: string; + repo: string; + ref: string; + path: string; + installed?: Map; + }; + + interface TsdInstalledItem { + commit: string; + }; + + interface PackageJson { + _requiredBy?: string[]; + dependencies?: Map; + devDependencies?: Map; + name: string; + optionalDependencies?: Map; + peerDependencies?: Map; + typings?: string; + }; + // A map of loose file names to library names // that we are confident require typings let safeList: Map; - const notFoundTypingNames: string[] = []; - - function tryParseJson(jsonPath: string, host: TypingResolutionHost): any { - if (host.fileExists(jsonPath)) { - try { - const contents = removeComments(host.readFile(jsonPath)); - return JSON.parse(contents); - } - catch (e) { } - } - return undefined; - } /** * @param host is the object providing I/O related operations. * @param fileNames are the file names that belong to the same project. - * @param globalCachePath is used to get the safe list file path and as cache path if the project root path isn't specified. - * @param projectRootPath is the path to the project root directory. This is used for the local typings cache. + * @param cachePath is the path to the typings cache + * @param projectRootPath is the path to the project root directory + * @param safeListPath is the path used to retrieve the safe list * @param typingOptions are used for customizing the typing inference process. * @param compilerOptions are used as a source of typing inference. */ export function discoverTypings( host: TypingResolutionHost, fileNames: string[], - globalCachePath: Path, + cachePath: Path, projectRootPath: Path, + safeListPath: Path, typingOptions: TypingOptions, compilerOptions: CompilerOptions): { cachedTypingPaths: string[], newTypingNames: string[], filesToWatch: string[] } { @@ -53,13 +65,12 @@ namespace ts.JsTyping { return { cachedTypingPaths: [], newTypingNames: [], filesToWatch: [] }; } - const cachePath = projectRootPath || globalCachePath; // Only infer typings for .js and .jsx files fileNames = filter(map(fileNames, normalizePath), f => scriptKindIs(f, /*LanguageServiceHost*/ undefined, ScriptKind.JS, ScriptKind.JSX)); - const safeListFilePath = combinePaths(globalCachePath, "safeList.json"); - if (!safeList && host.fileExists(safeListFilePath)) { - safeList = tryParseJson(safeListFilePath, host); + if (!safeList) { + const result = readConfigFile(safeListPath, host.readFile); + if (result.config) { safeList = result.config; } } const filesToWatch: string[] = []; @@ -86,26 +97,20 @@ namespace ts.JsTyping { const nodeModulesPath = combinePaths(searchDir, "node_modules"); getTypingNamesFromNodeModuleFolder(nodeModulesPath, filesToWatch); } - getTypingNamesFromSourceFileNames(fileNames); - getTypingNamesFromCompilerOptions(compilerOptions); } const typingsPath = combinePaths(cachePath, "typings"); const tsdJsonPath = combinePaths(cachePath, "tsd.json"); - const tsdJsonDict = tryParseJson(tsdJsonPath, host); - if (tsdJsonDict) { - for (const notFoundTypingName of notFoundTypingNames) { - if (hasProperty(inferredTypings, notFoundTypingName) && !inferredTypings[notFoundTypingName]) { - delete inferredTypings[notFoundTypingName]; - } - } + const result = readConfigFile(tsdJsonPath, host.readFile); + if (result.config) { + const tsdJson: TsdJson = result.config; // The "installed" property in the tsd.json serves as a registry of installed typings. Each item // of this object has a key of the relative file path, and a value that contains the corresponding // commit hash. - if (hasProperty(tsdJsonDict, "installed")) { - for (const cachedTypingPath in tsdJsonDict.installed) { + if (tsdJson.installed) { + for (const cachedTypingPath in tsdJson.installed) { // Assuming the cachedTypingPath has the format of "[package name]/[file name]" const cachedTypingName = cachedTypingPath.substr(0, cachedTypingPath.indexOf("/")); // If the inferred[cachedTypingName] is already not null, which means we found a corresponding @@ -153,20 +158,21 @@ namespace ts.JsTyping { * Get the typing info from common package manager json files like package.json or bower.json */ function getTypingNamesFromJson(jsonPath: string, filesToWatch: string[]) { - const jsonDict = tryParseJson(jsonPath, host); - if (jsonDict) { + const result = readConfigFile(jsonPath, host.readFile); + if (result.config) { + const jsonConfig: PackageJson = result.config; filesToWatch.push(jsonPath); - if (hasProperty(jsonDict, "dependencies")) { - mergeTypings(getKeys(jsonDict.dependencies)); + if (jsonConfig.dependencies) { + mergeTypings(getKeys(jsonConfig.dependencies)); } - if (hasProperty(jsonDict, "devDependencies")) { - mergeTypings(getKeys(jsonDict.devDependencies)); + if (jsonConfig.devDependencies) { + mergeTypings(getKeys(jsonConfig.devDependencies)); } - if (hasProperty(jsonDict, "optionalDependencies")) { - mergeTypings(getKeys(jsonDict.optionalDependencies)); + if (jsonConfig.optionalDependencies) { + mergeTypings(getKeys(jsonConfig.optionalDependencies)); } - if (hasProperty(jsonDict, "peerDependencies")) { - mergeTypings(getKeys(jsonDict.peerDependencies)); + if (jsonConfig.peerDependencies) { + mergeTypings(getKeys(jsonConfig.peerDependencies)); } } } @@ -205,75 +211,36 @@ namespace ts.JsTyping { } const typingNames: string[] = []; - const jsonFiles = host.readDirectory(nodeModulesPath, "*.json", /*exclude*/ undefined, /*depth*/ 2); - for (const jsonFile of jsonFiles) { - if (getBaseFileName(jsonFile) !== "package.json") { continue; } - const packageJsonDict = tryParseJson(jsonFile, host); - if (!packageJsonDict) { continue; } + const fileNames = host.readDirectory(nodeModulesPath, "*.json", /*exclude*/ undefined, /*depth*/ 2); + for (const fileName of fileNames) { + const normalizedFileName = normalizePath(fileName); + if (getBaseFileName(normalizedFileName) !== "package.json") { continue; } + const result = readConfigFile(normalizedFileName, host.readFile); + if (!result.config) { continue; } + const packageJson: PackageJson = result.config; + filesToWatch.push(normalizedFileName); - filesToWatch.push(jsonFile); - - // npm 3 has the package.json contains a "_requiredBy" field + // npm 3's package.json contains a "_requiredBy" field // we should include all the top level module names for npm 2, and only module names whose // "_requiredBy" field starts with "#" or equals "/" for npm 3. - if (packageJsonDict._requiredBy && - filter(packageJsonDict._requiredBy, (r: string) => r[0] === "#" || r === "/").length === 0) { + if (packageJson._requiredBy && + filter(packageJson._requiredBy, (r: string) => r[0] === "#" || r === "/").length === 0) { continue; } // If the package has its own d.ts typings, those will take precedence. Otherwise the package name will be used // to download d.ts files from DefinitelyTyped - const packageName = packageJsonDict["name"]; - if (hasProperty(packageJsonDict, "typings")) { - const absolutePath = getNormalizedAbsolutePath(packageJsonDict.typings, getDirectoryPath(jsonFile)); - inferredTypings[packageName] = absolutePath; + if (!packageJson.name) { continue; } + if (packageJson.typings) { + const absolutePath = getNormalizedAbsolutePath(packageJson.typings, getDirectoryPath(normalizedFileName)); + inferredTypings[packageJson.name] = absolutePath; } else { - typingNames.push(packageName); + typingNames.push(packageJson.name); } } mergeTypings(typingNames); } - function getTypingNamesFromCompilerOptions(options: CompilerOptions) { - const typingNames: string[] = []; - if (!options) { - return; - } - mergeTypings(typingNames); - } - } - - /** - * Keep a list of typings names that we know cannot be obtained at the moment (could be because - * of network issues or because the package doesn't hava a d.ts file in DefinitelyTyped), so - * that we won't try again next time within this session. - * @param newTypingNames The list of new typings that the host attempted to acquire - * @param cachePath The path to the tsd.json cache - * @param host The object providing I/O related operations. - */ - export function updateNotFoundTypingNames(newTypingNames: string[], cachePath: string, host: TypingResolutionHost): void { - const tsdJsonPath = combinePaths(cachePath, "tsd.json"); - const cacheTsdJsonDict = tryParseJson(tsdJsonPath, host); - if (cacheTsdJsonDict) { - const installedTypingFiles = hasProperty(cacheTsdJsonDict, "installed") - ? getKeys(cacheTsdJsonDict.installed) - : []; - const newMissingTypingNames = - filter(newTypingNames, name => notFoundTypingNames.indexOf(name) < 0 && !isInstalled(name, installedTypingFiles)); - for (const newMissingTypingName of newMissingTypingNames) { - notFoundTypingNames.push(newMissingTypingName); - } - } - } - - function isInstalled(typing: string, installedKeys: string[]) { - const typingPrefix = typing + "/"; - for (const key of installedKeys) { - if (key.indexOf(typingPrefix) === 0) { - return true; - } - } - return false; } } diff --git a/src/services/shims.ts b/src/services/shims.ts index 47a64ab58f6..6ba9b04c276 100644 --- a/src/services/shims.ts +++ b/src/services/shims.ts @@ -232,8 +232,7 @@ namespace ts { getPreProcessedFileInfo(fileName: string, sourceText: IScriptSnapshot): string; getTSConfigFileInfo(fileName: string, sourceText: IScriptSnapshot): string; getDefaultCompilationSettings(): string; - discoverTypings(fileNamesJson: string, globalCachePath: string, projectRootPath: string, typingOptionsJson: string, compilerOptionsJson: string): string; - updateNotFoundTypingNames(newTypingsJson: string, globalCachePath: string, projectRootPath: string): string; + discoverTypings(fileNamesJson: string, cachePath: string, projectRootPath: string, safeListPath: string, typingOptionsJson: string, compilerOptionsJson: string): string; } function logInternalError(logger: Logger, err: Error) { @@ -988,31 +987,22 @@ namespace ts { ); } - public discoverTypings(fileNamesJson: string, globalCachePath: string, projectRootPath: string, typingOptionsJson: string, compilerOptionsJson: string): string { + public discoverTypings(fileNamesJson: string, cachePath: string, projectRootPath: string, safeListPath: string, typingOptionsJson: string, compilerOptionsJson: string): string { const getCanonicalFileName = createGetCanonicalFileName(/*useCaseSensitivefileNames:*/ false); return this.forwardJSONCall("discoverTypings()", () => { - const cachePath = projectRootPath ? projectRootPath : globalCachePath; const typingOptions = JSON.parse(typingOptionsJson); - const compilerOptions = JSON.parse(compilerOptionsJson); const fileNames: string[] = JSON.parse(fileNamesJson); return ts.JsTyping.discoverTypings( this.host, fileNames, - toPath(globalCachePath, globalCachePath, getCanonicalFileName), toPath(cachePath, cachePath, getCanonicalFileName), + toPath(projectRootPath, projectRootPath, getCanonicalFileName), + toPath(safeListPath, safeListPath, getCanonicalFileName), typingOptions, compilerOptions); }); } - - public updateNotFoundTypingNames(newTypingsJson: string, globalCachePath: string, projectRootPath: string): string { - return this.forwardJSONCall("updateNotFoundTypingNames()", () => { - const newTypingNames: string[] = JSON.parse(newTypingsJson); - const cachePath = projectRootPath ? projectRootPath : globalCachePath; - ts.JsTyping.updateNotFoundTypingNames(newTypingNames, cachePath, this.host); - }); - } } export class TypeScriptServicesFactory implements ShimFactory { From 6aad783db800cd17d3edbee00a4a18b31db1ff1f Mon Sep 17 00:00:00 2001 From: Jason Ramsay Date: Tue, 1 Mar 2016 18:52:11 -0800 Subject: [PATCH 13/15] - Adding DiscoverTypingsSettings - Remove all references to Tsd. Instead pass a map of package names to cached typing locations --- src/compiler/types.ts | 12 +++++- src/services/jsTyping.ts | 82 ++++++++++++++-------------------------- src/services/shims.ts | 21 +++++----- 3 files changed, 49 insertions(+), 66 deletions(-) diff --git a/src/compiler/types.ts b/src/compiler/types.ts index c838a852647..0be74856b1d 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -2436,7 +2436,17 @@ namespace ts { enableAutoDiscovery?: boolean; include?: string[]; exclude?: string[]; - [option: string]: any; + [option: string]: string[] | boolean; + } + + export interface DiscoverTypingsSettings { + fileNames: string[]; // The file names that belong to the same project. + cachePath: string; // The path to the typings cache + projectRootPath: string; // The path to the project root directory + safeListPath: string; // The path used to retrieve the safe list + packageNameToTypingLocation: Map; // The map of package names to their cached typing locations + typingOptions: TypingOptions; // Used to customize the typing inference process + compilerOptions: CompilerOptions; // Used as a source for typing inference } export enum ModuleKind { diff --git a/src/services/jsTyping.ts b/src/services/jsTyping.ts index 786a199eb43..3866296e2f8 100644 --- a/src/services/jsTyping.ts +++ b/src/services/jsTyping.ts @@ -13,23 +13,11 @@ namespace ts.JsTyping { readDirectory: (path: string, extension?: string, exclude?: string[], depth?: number) => string[]; }; - interface TsdJson { - version: string; - repo: string; - ref: string; - path: string; - installed?: Map; - }; - - interface TsdInstalledItem { - commit: string; - }; - interface PackageJson { _requiredBy?: string[]; dependencies?: Map; devDependencies?: Map; - name: string; + name?: string; optionalDependencies?: Map; peerDependencies?: Map; typings?: string; @@ -41,12 +29,13 @@ namespace ts.JsTyping { /** * @param host is the object providing I/O related operations. - * @param fileNames are the file names that belong to the same project. + * @param fileNames are the file names that belong to the same project * @param cachePath is the path to the typings cache * @param projectRootPath is the path to the project root directory * @param safeListPath is the path used to retrieve the safe list - * @param typingOptions are used for customizing the typing inference process. - * @param compilerOptions are used as a source of typing inference. + * @param packageNameToTypingLocation is the map of package names to their cached typing locations + * @param typingOptions are used to customize the typing inference process + * @param compilerOptions are used as a source for typing inference */ export function discoverTypings( host: TypingResolutionHost, @@ -54,6 +43,7 @@ namespace ts.JsTyping { cachePath: Path, projectRootPath: Path, safeListPath: Path, + packageNameToTypingLocation: Map, typingOptions: TypingOptions, compilerOptions: CompilerOptions): { cachedTypingPaths: string[], newTypingNames: string[], filesToWatch: string[] } { @@ -69,8 +59,9 @@ namespace ts.JsTyping { fileNames = filter(map(fileNames, normalizePath), f => scriptKindIs(f, /*LanguageServiceHost*/ undefined, ScriptKind.JS, ScriptKind.JSX)); if (!safeList) { - const result = readConfigFile(safeListPath, host.readFile); + const result = readConfigFile(safeListPath, (path: string) => host.readFile(path)); if (result.config) { safeList = result.config; } + else { safeList = {}; }; } const filesToWatch: string[] = []; @@ -81,44 +72,27 @@ namespace ts.JsTyping { mergeTypings(typingOptions.include); exclude = typingOptions.exclude || []; - if (typingOptions.enableAutoDiscovery) { - const possibleSearchDirs = map(fileNames, getDirectoryPath); - if (projectRootPath !== undefined) { - possibleSearchDirs.push(projectRootPath); - } - searchDirs = deduplicate(possibleSearchDirs); - for (const searchDir of searchDirs) { - const packageJsonPath = combinePaths(searchDir, "package.json"); - getTypingNamesFromJson(packageJsonPath, filesToWatch); - - const bowerJsonPath = combinePaths(searchDir, "bower.json"); - getTypingNamesFromJson(bowerJsonPath, filesToWatch); - - const nodeModulesPath = combinePaths(searchDir, "node_modules"); - getTypingNamesFromNodeModuleFolder(nodeModulesPath, filesToWatch); - } - getTypingNamesFromSourceFileNames(fileNames); + const possibleSearchDirs = map(fileNames, getDirectoryPath); + if (projectRootPath !== undefined) { + possibleSearchDirs.push(projectRootPath); } + searchDirs = deduplicate(possibleSearchDirs); + for (const searchDir of searchDirs) { + const packageJsonPath = combinePaths(searchDir, "package.json"); + getTypingNamesFromJson(packageJsonPath, filesToWatch); - const typingsPath = combinePaths(cachePath, "typings"); - const tsdJsonPath = combinePaths(cachePath, "tsd.json"); - const result = readConfigFile(tsdJsonPath, host.readFile); - if (result.config) { - const tsdJson: TsdJson = result.config; + const bowerJsonPath = combinePaths(searchDir, "bower.json"); + getTypingNamesFromJson(bowerJsonPath, filesToWatch); - // The "installed" property in the tsd.json serves as a registry of installed typings. Each item - // of this object has a key of the relative file path, and a value that contains the corresponding - // commit hash. - if (tsdJson.installed) { - for (const cachedTypingPath in tsdJson.installed) { - // Assuming the cachedTypingPath has the format of "[package name]/[file name]" - const cachedTypingName = cachedTypingPath.substr(0, cachedTypingPath.indexOf("/")); - // If the inferred[cachedTypingName] is already not null, which means we found a corresponding - // d.ts file that coming with the package. That one should take higher priority. - if (hasProperty(inferredTypings, cachedTypingName) && !inferredTypings[cachedTypingName]) { - inferredTypings[cachedTypingName] = combinePaths(typingsPath, cachedTypingPath); - } - } + const nodeModulesPath = combinePaths(searchDir, "node_modules"); + getTypingNamesFromNodeModuleFolder(nodeModulesPath, filesToWatch); + } + getTypingNamesFromSourceFileNames(fileNames); + + // Add the cached typing locations for inferred typings that are already installed + for (const name in packageNameToTypingLocation) { + if (hasProperty(inferredTypings, name) && !inferredTypings[name]) { + inferredTypings[name] = packageNameToTypingLocation[name]; } } @@ -158,7 +132,7 @@ namespace ts.JsTyping { * Get the typing info from common package manager json files like package.json or bower.json */ function getTypingNamesFromJson(jsonPath: string, filesToWatch: string[]) { - const result = readConfigFile(jsonPath, host.readFile); + const result = readConfigFile(jsonPath, (path: string) => host.readFile(path)); if (result.config) { const jsonConfig: PackageJson = result.config; filesToWatch.push(jsonPath); @@ -215,7 +189,7 @@ namespace ts.JsTyping { for (const fileName of fileNames) { const normalizedFileName = normalizePath(fileName); if (getBaseFileName(normalizedFileName) !== "package.json") { continue; } - const result = readConfigFile(normalizedFileName, host.readFile); + const result = readConfigFile(normalizedFileName, (path: string) => host.readFile(path)); if (!result.config) { continue; } const packageJson: PackageJson = result.config; filesToWatch.push(normalizedFileName); diff --git a/src/services/shims.ts b/src/services/shims.ts index 6ba9b04c276..684c206b994 100644 --- a/src/services/shims.ts +++ b/src/services/shims.ts @@ -232,7 +232,7 @@ namespace ts { getPreProcessedFileInfo(fileName: string, sourceText: IScriptSnapshot): string; getTSConfigFileInfo(fileName: string, sourceText: IScriptSnapshot): string; getDefaultCompilationSettings(): string; - discoverTypings(fileNamesJson: string, cachePath: string, projectRootPath: string, safeListPath: string, typingOptionsJson: string, compilerOptionsJson: string): string; + discoverTypings(discoverTypingsJson: string): string; } function logInternalError(logger: Logger, err: Error) { @@ -987,20 +987,19 @@ namespace ts { ); } - public discoverTypings(fileNamesJson: string, cachePath: string, projectRootPath: string, safeListPath: string, typingOptionsJson: string, compilerOptionsJson: string): string { + public discoverTypings(discoverTypingsJson: string): string { const getCanonicalFileName = createGetCanonicalFileName(/*useCaseSensitivefileNames:*/ false); return this.forwardJSONCall("discoverTypings()", () => { - const typingOptions = JSON.parse(typingOptionsJson); - const compilerOptions = JSON.parse(compilerOptionsJson); - const fileNames: string[] = JSON.parse(fileNamesJson); + const settings = JSON.parse(discoverTypingsJson); return ts.JsTyping.discoverTypings( this.host, - fileNames, - toPath(cachePath, cachePath, getCanonicalFileName), - toPath(projectRootPath, projectRootPath, getCanonicalFileName), - toPath(safeListPath, safeListPath, getCanonicalFileName), - typingOptions, - compilerOptions); + settings.fileNames, + toPath(settings.cachePath, settings.cachePath, getCanonicalFileName), + toPath(settings.projectRootPath, settings.projectRootPath, getCanonicalFileName), + toPath(settings.safeListPath, settings.safeListPath, getCanonicalFileName), + settings.packageNameToTypingLocation, + settings.typingOptions, + settings.compilerOptions); }); } } From 4bbdf2a0bb3553f1d4fa20b719e3f3fc952179cc Mon Sep 17 00:00:00 2001 From: Jason Ramsay Date: Tue, 1 Mar 2016 19:06:31 -0800 Subject: [PATCH 14/15] - Removing filesToWatch from getTypingNamesFromNodeModuleFolder. These modules are already installed and are not expected to change --- src/services/jsTyping.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/services/jsTyping.ts b/src/services/jsTyping.ts index 3866296e2f8..01cc3eed3b1 100644 --- a/src/services/jsTyping.ts +++ b/src/services/jsTyping.ts @@ -85,7 +85,7 @@ namespace ts.JsTyping { getTypingNamesFromJson(bowerJsonPath, filesToWatch); const nodeModulesPath = combinePaths(searchDir, "node_modules"); - getTypingNamesFromNodeModuleFolder(nodeModulesPath, filesToWatch); + getTypingNamesFromNodeModuleFolder(nodeModulesPath); } getTypingNamesFromSourceFileNames(fileNames); @@ -178,7 +178,7 @@ namespace ts.JsTyping { * Infer typing names from node_module folder * @param nodeModulesPath is the path to the "node_modules" folder */ - function getTypingNamesFromNodeModuleFolder(nodeModulesPath: string, filesToWatch: string[]) { + function getTypingNamesFromNodeModuleFolder(nodeModulesPath: string) { // Todo: add support for ModuleResolutionHost too if (!host.directoryExists(nodeModulesPath)) { return; @@ -192,7 +192,6 @@ namespace ts.JsTyping { const result = readConfigFile(normalizedFileName, (path: string) => host.readFile(path)); if (!result.config) { continue; } const packageJson: PackageJson = result.config; - filesToWatch.push(normalizedFileName); // npm 3's package.json contains a "_requiredBy" field // we should include all the top level module names for npm 2, and only module names whose From e8772bc0a216023ae978b619fcd29a5567506225 Mon Sep 17 00:00:00 2001 From: Jason Ramsay Date: Wed, 2 Mar 2016 10:11:13 -0800 Subject: [PATCH 15/15] - Adding new lines after { for single-line if statements - Renaming DiscoverTypingsSettings to DiscoverTypingsInfo to match host --- src/compiler/types.ts | 2 +- src/services/jsTyping.ts | 20 +++++++++++++++----- src/services/shims.ts | 16 ++++++++-------- 3 files changed, 24 insertions(+), 14 deletions(-) diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 0be74856b1d..4d843618281 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -2439,7 +2439,7 @@ namespace ts { [option: string]: string[] | boolean; } - export interface DiscoverTypingsSettings { + export interface DiscoverTypingsInfo { fileNames: string[]; // The file names that belong to the same project. cachePath: string; // The path to the typings cache projectRootPath: string; // The path to the project root directory diff --git a/src/services/jsTyping.ts b/src/services/jsTyping.ts index 01cc3eed3b1..22265cc1016 100644 --- a/src/services/jsTyping.ts +++ b/src/services/jsTyping.ts @@ -60,8 +60,12 @@ namespace ts.JsTyping { if (!safeList) { const result = readConfigFile(safeListPath, (path: string) => host.readFile(path)); - if (result.config) { safeList = result.config; } - else { safeList = {}; }; + if (result.config) { + safeList = result.config; + } + else { + safeList = {}; + }; } const filesToWatch: string[] = []; @@ -188,9 +192,13 @@ namespace ts.JsTyping { const fileNames = host.readDirectory(nodeModulesPath, "*.json", /*exclude*/ undefined, /*depth*/ 2); for (const fileName of fileNames) { const normalizedFileName = normalizePath(fileName); - if (getBaseFileName(normalizedFileName) !== "package.json") { continue; } + if (getBaseFileName(normalizedFileName) !== "package.json") { + continue; + } const result = readConfigFile(normalizedFileName, (path: string) => host.readFile(path)); - if (!result.config) { continue; } + if (!result.config) { + continue; + } const packageJson: PackageJson = result.config; // npm 3's package.json contains a "_requiredBy" field @@ -203,7 +211,9 @@ namespace ts.JsTyping { // If the package has its own d.ts typings, those will take precedence. Otherwise the package name will be used // to download d.ts files from DefinitelyTyped - if (!packageJson.name) { continue; } + if (!packageJson.name) { + continue; + } if (packageJson.typings) { const absolutePath = getNormalizedAbsolutePath(packageJson.typings, getDirectoryPath(normalizedFileName)); inferredTypings[packageJson.name] = absolutePath; diff --git a/src/services/shims.ts b/src/services/shims.ts index 684c206b994..25d0480de3e 100644 --- a/src/services/shims.ts +++ b/src/services/shims.ts @@ -990,16 +990,16 @@ namespace ts { public discoverTypings(discoverTypingsJson: string): string { const getCanonicalFileName = createGetCanonicalFileName(/*useCaseSensitivefileNames:*/ false); return this.forwardJSONCall("discoverTypings()", () => { - const settings = JSON.parse(discoverTypingsJson); + const info = JSON.parse(discoverTypingsJson); return ts.JsTyping.discoverTypings( this.host, - settings.fileNames, - toPath(settings.cachePath, settings.cachePath, getCanonicalFileName), - toPath(settings.projectRootPath, settings.projectRootPath, getCanonicalFileName), - toPath(settings.safeListPath, settings.safeListPath, getCanonicalFileName), - settings.packageNameToTypingLocation, - settings.typingOptions, - settings.compilerOptions); + info.fileNames, + toPath(info.cachePath, info.cachePath, getCanonicalFileName), + toPath(info.projectRootPath, info.projectRootPath, getCanonicalFileName), + toPath(info.safeListPath, info.safeListPath, getCanonicalFileName), + info.packageNameToTypingLocation, + info.typingOptions, + info.compilerOptions); }); } }