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 {