From 83af10b05c297f78c7a5aeb2c95a6dc987c988ca Mon Sep 17 00:00:00 2001 From: Jason Ramsay Date: Tue, 8 Mar 2016 13:43:02 -0800 Subject: [PATCH 01/26] Salsa: adding ScriptKind to ensure script blocks (.html, .aspx, .ascx, etc) are processed as JS --- src/compiler/parser.ts | 21 ++++++----- src/compiler/types.ts | 9 +++++ src/harness/harnessLanguageService.ts | 2 ++ src/server/editorServices.ts | 4 +++ src/services/services.ts | 52 +++++++++++++++------------ src/services/shims.ts | 9 +++++ 6 files changed, 66 insertions(+), 31 deletions(-) diff --git a/src/compiler/parser.ts b/src/compiler/parser.ts index 5def394578d..7a8dbe99bc6 100644 --- a/src/compiler/parser.ts +++ b/src/compiler/parser.ts @@ -399,14 +399,15 @@ namespace ts { } } - export function createSourceFile(fileName: string, sourceText: string, languageVersion: ScriptTarget, setParentNodes = false): SourceFile { + export function createSourceFile(fileName: string, sourceText: string, languageVersion: ScriptTarget, setParentNodes = false, scriptKind?: ScriptKind): SourceFile { const start = new Date().getTime(); - const result = Parser.parseSourceFile(fileName, sourceText, languageVersion, /*syntaxCursor*/ undefined, setParentNodes); + const result = Parser.parseSourceFile(fileName, sourceText, languageVersion, /*syntaxCursor*/ undefined, setParentNodes, scriptKind); parseTime += new Date().getTime() - start; return result; } + // 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 @@ -416,8 +417,8 @@ namespace ts { // from this SourceFile that are being held onto may change as a result (including // becoming detached from any SourceFile). It is recommended that this SourceFile not // be used once 'update' is called on it. - export function updateSourceFile(sourceFile: SourceFile, newText: string, textChangeRange: TextChangeRange, aggressiveChecks?: boolean): SourceFile { - return IncrementalParser.updateSourceFile(sourceFile, newText, textChangeRange, aggressiveChecks); + export function updateSourceFile(sourceFile: SourceFile, newText: string, textChangeRange: TextChangeRange, aggressiveChecks?: boolean, scriptKind?: ScriptKind): SourceFile { + return IncrementalParser.updateSourceFile(sourceFile, newText, textChangeRange, aggressiveChecks, scriptKind); } /* @internal */ @@ -533,8 +534,10 @@ namespace ts { // attached to the EOF token. let parseErrorBeforeNextFinishedNode = false; - export function parseSourceFile(fileName: string, _sourceText: string, languageVersion: ScriptTarget, _syntaxCursor: IncrementalParser.SyntaxCursor, setParentNodes?: boolean): SourceFile { - const isJavaScriptFile = hasJavaScriptFileExtension(fileName) || _sourceText.lastIndexOf("// @language=javascript", 0) === 0; + export function parseSourceFile(fileName: string, _sourceText: string, languageVersion: ScriptTarget, _syntaxCursor: IncrementalParser.SyntaxCursor, setParentNodes?: boolean, scriptKind?: ScriptKind): SourceFile { + + const isJavaScriptFile = hasJavaScriptFileExtension(fileName) || _sourceText.lastIndexOf("// @language=javascript", 0) === 0 || scriptKind == ScriptKind.Js; + initializeState(fileName, _sourceText, languageVersion, isJavaScriptFile, _syntaxCursor); const result = parseSourceFileWorker(fileName, languageVersion, setParentNodes); @@ -6237,7 +6240,7 @@ namespace ts { } namespace IncrementalParser { - export function updateSourceFile(sourceFile: SourceFile, newText: string, textChangeRange: TextChangeRange, aggressiveChecks: boolean): SourceFile { + export function updateSourceFile(sourceFile: SourceFile, newText: string, textChangeRange: TextChangeRange, aggressiveChecks: boolean, scriptKind?: ScriptKind): SourceFile { aggressiveChecks = aggressiveChecks || Debug.shouldAssert(AssertionLevel.Aggressive); checkChangeRange(sourceFile, newText, textChangeRange, aggressiveChecks); @@ -6249,7 +6252,7 @@ namespace ts { if (sourceFile.statements.length === 0) { // If we don't have any statements in the current source file, then there's no real // way to incrementally parse. So just do a full parse instead. - return Parser.parseSourceFile(sourceFile.fileName, newText, sourceFile.languageVersion, /*syntaxCursor*/ undefined, /*setParentNodes*/ true); + return Parser.parseSourceFile(sourceFile.fileName, newText, sourceFile.languageVersion, /*syntaxCursor*/ undefined, /*setParentNodes*/ true, scriptKind); } // Make sure we're not trying to incrementally update a source file more than once. Once @@ -6313,7 +6316,7 @@ namespace ts { // inconsistent tree. Setting the parents on the new tree should be very fast. We // will immediately bail out of walking any subtrees when we can see that their parents // are already correct. - const result = Parser.parseSourceFile(sourceFile.fileName, newText, sourceFile.languageVersion, syntaxCursor, /*setParentNodes*/ true); + const result = Parser.parseSourceFile(sourceFile.fileName, newText, sourceFile.languageVersion, syntaxCursor, /*setParentNodes*/ true, scriptKind); return result; } diff --git a/src/compiler/types.ts b/src/compiler/types.ts index d605a684a76..5f01c2a92b0 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -1,3 +1,4 @@ + namespace ts { export interface Map { [index: string]: T; @@ -2476,6 +2477,14 @@ namespace ts { character: number; } + export const enum ScriptKind { + Unknown = 0, + Js = 1, + Jsx = 2, + Ts = 3, + Tsx = 4 + } + export const enum ScriptTarget { ES3 = 0, ES5 = 1, diff --git a/src/harness/harnessLanguageService.ts b/src/harness/harnessLanguageService.ts index a0cac439729..5887d77ab62 100644 --- a/src/harness/harnessLanguageService.ts +++ b/src/harness/harnessLanguageService.ts @@ -183,6 +183,7 @@ namespace Harness.LanguageService { const script = this.getScriptInfo(fileName); return script ? new ScriptSnapshot(script) : undefined; } + getScriptKind(fileName: string): ScriptKind { return ScriptKind.Unknown; } getScriptVersion(fileName: string): string { const script = this.getScriptInfo(fileName); return script ? script.version.toString() : undefined; @@ -253,6 +254,7 @@ namespace Harness.LanguageService { const nativeScriptSnapshot = this.nativeHost.getScriptSnapshot(fileName); return nativeScriptSnapshot && new ScriptSnapshotProxy(nativeScriptSnapshot); } + getScriptKind(fileName: string): ScriptKind { return this.nativeHost.getScriptKind(fileName); } getScriptVersion(fileName: string): string { return this.nativeHost.getScriptVersion(fileName); } getLocalizedDiagnosticMessages(): string { return JSON.stringify({}); } diff --git a/src/server/editorServices.ts b/src/server/editorServices.ts index e2ec5cc159f..71907735b91 100644 --- a/src/server/editorServices.ts +++ b/src/server/editorServices.ts @@ -192,6 +192,10 @@ namespace ts.server { return this.roots.map(root => root.fileName); } + getScriptKind() { + return ScriptKind.Unknown; + } + getScriptVersion(filename: string) { return this.getScriptInfo(filename).svc.latestVersion().toString(); } diff --git a/src/services/services.ts b/src/services/services.ts index 0fb7f329dfe..5b4d0e77f42 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -818,8 +818,8 @@ namespace ts { super(kind, pos, end); } - public update(newText: string, textChangeRange: TextChangeRange): SourceFile { - return updateSourceFile(this, newText, textChangeRange); + public update(newText: string, textChangeRange: TextChangeRange, scriptKind?: ScriptKind): SourceFile { + return updateSourceFile(this, newText, textChangeRange, /*aggressiveChecks*/ undefined, scriptKind); } public getLineAndCharacterOfPosition(position: number): LineAndCharacter { @@ -1018,6 +1018,7 @@ namespace ts { getNewLine?(): string; getProjectVersion?(): string; getScriptFileNames(): string[]; + getScriptKind(fileName: string): ScriptKind; getScriptVersion(fileName: string): string; getScriptSnapshot(fileName: string): IScriptSnapshot; getLocalizedDiagnosticMessages?(): any; @@ -1467,7 +1468,8 @@ namespace ts { fileName: string, compilationSettings: CompilerOptions, scriptSnapshot: IScriptSnapshot, - version: string): SourceFile; + version: string, + scriptKind?: ScriptKind): SourceFile; /** * Request an updated version of an already existing SourceFile with a given fileName @@ -1485,7 +1487,8 @@ namespace ts { fileName: string, compilationSettings: CompilerOptions, scriptSnapshot: IScriptSnapshot, - version: string): SourceFile; + version: string, + scriptKind?: ScriptKind): SourceFile; /** * Informs the DocumentRegistry that a file is not needed any longer. @@ -1656,6 +1659,7 @@ namespace ts { hostFileName: string; version: string; scriptSnapshot: IScriptSnapshot; + scriptKind: ScriptKind; } interface DocumentRegistryEntry { @@ -1745,12 +1749,14 @@ namespace ts { private createEntry(fileName: string, path: Path) { let entry: HostFileInformation; + const scriptKind = this.host.getScriptKind(fileName); const scriptSnapshot = this.host.getScriptSnapshot(fileName); if (scriptSnapshot) { entry = { hostFileName: fileName, version: this.host.getScriptVersion(fileName), - scriptSnapshot: scriptSnapshot + scriptSnapshot: scriptSnapshot, + scriptKind: scriptKind }; } @@ -1816,17 +1822,18 @@ namespace ts { throw new Error("Could not find file: '" + fileName + "'."); } + const scriptKind = this.host.getScriptKind(fileName); const version = this.host.getScriptVersion(fileName); let sourceFile: SourceFile; if (this.currentFileName !== fileName) { // This is a new file, just parse it - sourceFile = createLanguageServiceSourceFile(fileName, scriptSnapshot, ScriptTarget.Latest, version, /*setNodeParents*/ true); + sourceFile = createLanguageServiceSourceFile(fileName, scriptSnapshot, ScriptTarget.Latest, version, /*setNodeParents*/ true, scriptKind); } else if (this.currentFileVersion !== version) { // This is the same file, just a newer version. Incrementally parse the file. const editRange = scriptSnapshot.getChangeRange(this.currentFileScriptSnapshot); - sourceFile = updateLanguageServiceSourceFile(this.currentSourceFile, scriptSnapshot, version, editRange); + sourceFile = updateLanguageServiceSourceFile(this.currentSourceFile, scriptSnapshot, version, editRange, /*aggressiveChecks*/ undefined, scriptKind); } if (sourceFile) { @@ -1952,16 +1959,16 @@ namespace ts { return output.outputText; } - export function createLanguageServiceSourceFile(fileName: string, scriptSnapshot: IScriptSnapshot, scriptTarget: ScriptTarget, version: string, setNodeParents: boolean): SourceFile { + export function createLanguageServiceSourceFile(fileName: string, scriptSnapshot: IScriptSnapshot, scriptTarget: ScriptTarget, version: string, setNodeParents: boolean, scriptKind?: ScriptKind): SourceFile { const text = scriptSnapshot.getText(0, scriptSnapshot.getLength()); - const sourceFile = createSourceFile(fileName, text, scriptTarget, setNodeParents); + const sourceFile = createSourceFile(fileName, text, scriptTarget, setNodeParents, scriptKind); setSourceFileFields(sourceFile, scriptSnapshot, version); return sourceFile; } export let disableIncrementalParsing = false; - export function updateLanguageServiceSourceFile(sourceFile: SourceFile, scriptSnapshot: IScriptSnapshot, version: string, textChangeRange: TextChangeRange, aggressiveChecks?: boolean): SourceFile { + export function updateLanguageServiceSourceFile(sourceFile: SourceFile, scriptSnapshot: IScriptSnapshot, version: string, textChangeRange: TextChangeRange, aggressiveChecks?: boolean, scriptKind?: ScriptKind): SourceFile { // If we were given a text change range, and our version or open-ness changed, then // incrementally parse this file. if (textChangeRange) { @@ -1995,7 +2002,7 @@ namespace ts { : (changedText + suffix); } - const newSourceFile = updateSourceFile(sourceFile, newText, textChangeRange, aggressiveChecks); + const newSourceFile = updateSourceFile(sourceFile, newText, textChangeRange, aggressiveChecks, scriptKind); setSourceFileFields(newSourceFile, scriptSnapshot, version); // after incremental parsing nameTable might not be up-to-date // drop it so it can be lazily recreated later @@ -2016,7 +2023,7 @@ namespace ts { } // Otherwise, just create a new source file. - return createLanguageServiceSourceFile(sourceFile.fileName, scriptSnapshot, sourceFile.languageVersion, version, /*setNodeParents*/ true); + return createLanguageServiceSourceFile(sourceFile.fileName, scriptSnapshot, sourceFile.languageVersion, version, /*setNodeParents*/ true, scriptKind); } export function createDocumentRegistry(useCaseSensitiveFileNames?: boolean, currentDirectory = ""): DocumentRegistry { @@ -2058,12 +2065,12 @@ namespace ts { return JSON.stringify(bucketInfoArray, undefined, 2); } - function acquireDocument(fileName: string, compilationSettings: CompilerOptions, scriptSnapshot: IScriptSnapshot, version: string): SourceFile { - return acquireOrUpdateDocument(fileName, compilationSettings, scriptSnapshot, version, /*acquiring*/ true); + function acquireDocument(fileName: string, compilationSettings: CompilerOptions, scriptSnapshot: IScriptSnapshot, version: string, scriptKind?: ScriptKind): SourceFile { + return acquireOrUpdateDocument(fileName, compilationSettings, scriptSnapshot, version, /*acquiring*/ true, scriptKind); } - function updateDocument(fileName: string, compilationSettings: CompilerOptions, scriptSnapshot: IScriptSnapshot, version: string): SourceFile { - return acquireOrUpdateDocument(fileName, compilationSettings, scriptSnapshot, version, /*acquiring*/ false); + function updateDocument(fileName: string, compilationSettings: CompilerOptions, scriptSnapshot: IScriptSnapshot, version: string, scriptKind?: ScriptKind): SourceFile { + return acquireOrUpdateDocument(fileName, compilationSettings, scriptSnapshot, version, /*acquiring*/ false, scriptKind); } function acquireOrUpdateDocument( @@ -2071,7 +2078,8 @@ namespace ts { compilationSettings: CompilerOptions, scriptSnapshot: IScriptSnapshot, version: string, - acquiring: boolean): SourceFile { + acquiring: boolean, + scriptKind?: ScriptKind): SourceFile { const bucket = getBucketForCompilationSettings(compilationSettings, /*createIfMissing*/ true); const path = toPath(fileName, currentDirectory, getCanonicalFileName); @@ -2080,7 +2088,7 @@ namespace ts { Debug.assert(acquiring, "How could we be trying to update a document that the registry doesn't have?"); // Have never seen this file with these settings. Create a new source file for it. - const sourceFile = createLanguageServiceSourceFile(fileName, scriptSnapshot, compilationSettings.target, version, /*setNodeParents*/ false); + const sourceFile = createLanguageServiceSourceFile(fileName, scriptSnapshot, compilationSettings.target, version, /*setNodeParents*/ false, scriptKind); entry = { sourceFile: sourceFile, @@ -2094,8 +2102,8 @@ namespace ts { // the script snapshot. If so, update it appropriately. Otherwise, we can just // return it as is. if (entry.sourceFile.version !== version) { - entry.sourceFile = updateLanguageServiceSourceFile(entry.sourceFile, scriptSnapshot, version, - scriptSnapshot.getChangeRange(entry.sourceFile.scriptSnapshot)); + entry.sourceFile = updateLanguageServiceSourceFile(entry.sourceFile, scriptSnapshot, version, + scriptSnapshot.getChangeRange(entry.sourceFile.scriptSnapshot), /*aggressiveChecks*/ undefined, scriptKind); } } @@ -2844,14 +2852,14 @@ namespace ts { // it's source file any more, and instead defers to DocumentRegistry to get // either version 1, version 2 (or some other version) depending on what the // host says should be used. - return documentRegistry.updateDocument(fileName, newSettings, hostFileInformation.scriptSnapshot, hostFileInformation.version); + return documentRegistry.updateDocument(fileName, newSettings, hostFileInformation.scriptSnapshot, hostFileInformation.version, hostFileInformation.scriptKind); } // We didn't already have the file. Fall through and acquire it from the registry. } // Could not find this file in the old program, create a new SourceFile for it. - return documentRegistry.acquireDocument(fileName, newSettings, hostFileInformation.scriptSnapshot, hostFileInformation.version); + return documentRegistry.acquireDocument(fileName, newSettings, hostFileInformation.scriptSnapshot, hostFileInformation.version, hostFileInformation.scriptKind); } function sourceFileUpToDate(sourceFile: SourceFile): boolean { diff --git a/src/services/shims.ts b/src/services/shims.ts index 9ca3f19244d..e29b15be4b1 100644 --- a/src/services/shims.ts +++ b/src/services/shims.ts @@ -55,6 +55,7 @@ namespace ts { /** Returns a JSON-encoded value of the type: string[] */ getScriptFileNames(): string; + getScriptKind(fileName: string): ScriptKind; getScriptVersion(fileName: string): string; getScriptSnapshot(fileName: string): ScriptSnapshotShim; getLocalizedDiagnosticMessages(): string; @@ -344,6 +345,14 @@ namespace ts { return scriptSnapshot && new ScriptSnapshotShimAdapter(scriptSnapshot); } + public getScriptKind(fileName: string): ScriptKind { + try { + return this.shimHost.getScriptKind(fileName); + } catch (e) { + return ScriptKind.Unknown; + } + } + public getScriptVersion(fileName: string): string { return this.shimHost.getScriptVersion(fileName); } From 9b27c148e9b063758a24d21dc10b16210a7dc04c Mon Sep 17 00:00:00 2001 From: Jason Ramsay Date: Tue, 16 Feb 2016 22:44:43 -0800 Subject: [PATCH 02/26] Fix to test harness and addressing CR comments --- src/compiler/parser.ts | 2 +- src/compiler/types.ts | 8 ++++---- src/harness/harnessLanguageService.ts | 4 ++-- src/services/services.ts | 6 +++--- src/services/shims.ts | 5 +++-- 5 files changed, 13 insertions(+), 12 deletions(-) diff --git a/src/compiler/parser.ts b/src/compiler/parser.ts index 7a8dbe99bc6..1b59de63e36 100644 --- a/src/compiler/parser.ts +++ b/src/compiler/parser.ts @@ -536,7 +536,7 @@ namespace ts { export function parseSourceFile(fileName: string, _sourceText: string, languageVersion: ScriptTarget, _syntaxCursor: IncrementalParser.SyntaxCursor, setParentNodes?: boolean, scriptKind?: ScriptKind): SourceFile { - const isJavaScriptFile = hasJavaScriptFileExtension(fileName) || _sourceText.lastIndexOf("// @language=javascript", 0) === 0 || scriptKind == ScriptKind.Js; + const isJavaScriptFile = hasJavaScriptFileExtension(fileName) || _sourceText.lastIndexOf("// @language=javascript", 0) === 0 || scriptKind == ScriptKind.JS; initializeState(fileName, _sourceText, languageVersion, isJavaScriptFile, _syntaxCursor); diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 5f01c2a92b0..e76d4b01b54 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -2479,10 +2479,10 @@ namespace ts { export const enum ScriptKind { Unknown = 0, - Js = 1, - Jsx = 2, - Ts = 3, - Tsx = 4 + JS = 1, + JSX = 2, + TS = 3, + TSX = 4 } export const enum ScriptTarget { diff --git a/src/harness/harnessLanguageService.ts b/src/harness/harnessLanguageService.ts index 5887d77ab62..cb8f85eea31 100644 --- a/src/harness/harnessLanguageService.ts +++ b/src/harness/harnessLanguageService.ts @@ -183,7 +183,7 @@ namespace Harness.LanguageService { const script = this.getScriptInfo(fileName); return script ? new ScriptSnapshot(script) : undefined; } - getScriptKind(fileName: string): ScriptKind { return ScriptKind.Unknown; } + getScriptKind(fileName: string): ts.ScriptKind { return ts.ScriptKind.Unknown; } getScriptVersion(fileName: string): string { const script = this.getScriptInfo(fileName); return script ? script.version.toString() : undefined; @@ -254,7 +254,7 @@ namespace Harness.LanguageService { const nativeScriptSnapshot = this.nativeHost.getScriptSnapshot(fileName); return nativeScriptSnapshot && new ScriptSnapshotProxy(nativeScriptSnapshot); } - getScriptKind(fileName: string): ScriptKind { return this.nativeHost.getScriptKind(fileName); } + getScriptKind(fileName: string): ts.ScriptKind { return this.nativeHost.getScriptKind(fileName); } getScriptVersion(fileName: string): string { return this.nativeHost.getScriptVersion(fileName); } getLocalizedDiagnosticMessages(): string { return JSON.stringify({}); } diff --git a/src/services/services.ts b/src/services/services.ts index 5b4d0e77f42..0362c235ee9 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -1018,7 +1018,7 @@ namespace ts { getNewLine?(): string; getProjectVersion?(): string; getScriptFileNames(): string[]; - getScriptKind(fileName: string): ScriptKind; + getScriptKind?(fileName: string): ScriptKind; getScriptVersion(fileName: string): string; getScriptSnapshot(fileName: string): IScriptSnapshot; getLocalizedDiagnosticMessages?(): any; @@ -1749,7 +1749,7 @@ namespace ts { private createEntry(fileName: string, path: Path) { let entry: HostFileInformation; - const scriptKind = this.host.getScriptKind(fileName); + const scriptKind = this.host.getScriptKind ? this.host.getScriptKind(fileName) : ScriptKind.Unknown; const scriptSnapshot = this.host.getScriptSnapshot(fileName); if (scriptSnapshot) { entry = { @@ -1822,7 +1822,7 @@ namespace ts { throw new Error("Could not find file: '" + fileName + "'."); } - const scriptKind = this.host.getScriptKind(fileName); + const scriptKind = this.host.getScriptKind ? this.host.getScriptKind(fileName) : ScriptKind.Unknown; const version = this.host.getScriptVersion(fileName); let sourceFile: SourceFile; diff --git a/src/services/shims.ts b/src/services/shims.ts index e29b15be4b1..44d28aff286 100644 --- a/src/services/shims.ts +++ b/src/services/shims.ts @@ -55,7 +55,7 @@ namespace ts { /** Returns a JSON-encoded value of the type: string[] */ getScriptFileNames(): string; - getScriptKind(fileName: string): ScriptKind; + getScriptKind?(fileName: string): ScriptKind; getScriptVersion(fileName: string): string; getScriptSnapshot(fileName: string): ScriptSnapshotShim; getLocalizedDiagnosticMessages(): string; @@ -348,7 +348,8 @@ namespace ts { public getScriptKind(fileName: string): ScriptKind { try { return this.shimHost.getScriptKind(fileName); - } catch (e) { + } + catch (e) { return ScriptKind.Unknown; } } From cc02a805a9bf718ee8f4069e0d8109717170d394 Mon Sep 17 00:00:00 2001 From: Jason Ramsay Date: Wed, 17 Feb 2016 07:42:27 -0800 Subject: [PATCH 03/26] Removing trailing whitespace from services.ts --- src/services/services.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/services/services.ts b/src/services/services.ts index 0362c235ee9..fdc366e1574 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -2102,7 +2102,7 @@ namespace ts { // the script snapshot. If so, update it appropriately. Otherwise, we can just // return it as is. if (entry.sourceFile.version !== version) { - entry.sourceFile = updateLanguageServiceSourceFile(entry.sourceFile, scriptSnapshot, version, + entry.sourceFile = updateLanguageServiceSourceFile(entry.sourceFile, scriptSnapshot, version, scriptSnapshot.getChangeRange(entry.sourceFile.scriptSnapshot), /*aggressiveChecks*/ undefined, scriptKind); } } From f8353d4429c1aeb0f22d3ba91ad4f03164f76133 Mon Sep 17 00:00:00 2001 From: Jason Ramsay Date: Tue, 8 Mar 2016 13:48:41 -0800 Subject: [PATCH 04/26] Addressing CR comments - Adding ScriptKind to SourceFile - Only update ScriptKind on creation, on update use the SourceFile's scriptKind - If scriptKind is unknown, default to ScriptKind.Ts - Replacing try-catch with in this.shimHost --- src/compiler/parser.ts | 24 +++++++++++++----------- src/compiler/types.ts | 1 + src/services/services.ts | 15 ++++++++------- src/services/shims.ts | 4 ++-- 4 files changed, 24 insertions(+), 20 deletions(-) diff --git a/src/compiler/parser.ts b/src/compiler/parser.ts index 1b59de63e36..1d736fcf28d 100644 --- a/src/compiler/parser.ts +++ b/src/compiler/parser.ts @@ -417,8 +417,8 @@ namespace ts { // from this SourceFile that are being held onto may change as a result (including // becoming detached from any SourceFile). It is recommended that this SourceFile not // be used once 'update' is called on it. - export function updateSourceFile(sourceFile: SourceFile, newText: string, textChangeRange: TextChangeRange, aggressiveChecks?: boolean, scriptKind?: ScriptKind): SourceFile { - return IncrementalParser.updateSourceFile(sourceFile, newText, textChangeRange, aggressiveChecks, scriptKind); + export function updateSourceFile(sourceFile: SourceFile, newText: string, textChangeRange: TextChangeRange, aggressiveChecks?: boolean): SourceFile { + return IncrementalParser.updateSourceFile(sourceFile, newText, textChangeRange, aggressiveChecks); } /* @internal */ @@ -536,11 +536,10 @@ namespace ts { export function parseSourceFile(fileName: string, _sourceText: string, languageVersion: ScriptTarget, _syntaxCursor: IncrementalParser.SyntaxCursor, setParentNodes?: boolean, scriptKind?: ScriptKind): SourceFile { - const isJavaScriptFile = hasJavaScriptFileExtension(fileName) || _sourceText.lastIndexOf("// @language=javascript", 0) === 0 || scriptKind == ScriptKind.JS; - + const isJavaScriptFile = scriptKind === ScriptKind.JS || hasJavaScriptFileExtension(fileName); initializeState(fileName, _sourceText, languageVersion, isJavaScriptFile, _syntaxCursor); - const result = parseSourceFileWorker(fileName, languageVersion, setParentNodes); + const result = parseSourceFileWorker(fileName, languageVersion, setParentNodes, scriptKind); clearState(); @@ -588,8 +587,8 @@ namespace ts { sourceText = undefined; } - function parseSourceFileWorker(fileName: string, languageVersion: ScriptTarget, setParentNodes: boolean): SourceFile { - sourceFile = createSourceFile(fileName, languageVersion); + function parseSourceFileWorker(fileName: string, languageVersion: ScriptTarget, setParentNodes: boolean, scriptKind?: ScriptKind): SourceFile { + sourceFile = createSourceFile(fileName, languageVersion, scriptKind); if (contextFlags & ParserContextFlags.JavaScriptFile) { sourceFile.parserContextFlags = ParserContextFlags.JavaScriptFile; @@ -659,7 +658,7 @@ namespace ts { } } - function createSourceFile(fileName: string, languageVersion: ScriptTarget): SourceFile { + function createSourceFile(fileName: string, languageVersion: ScriptTarget, scriptKind?: ScriptKind): SourceFile { // code from createNode is inlined here so createNode won't have to deal with special case of creating source files // this is quite rare comparing to other nodes and createNode should be as fast as possible const sourceFile = new SourceFileConstructor(SyntaxKind.SourceFile, /*pos*/ 0, /* end */ sourceText.length); @@ -672,6 +671,9 @@ namespace ts { sourceFile.flags = fileExtensionIs(sourceFile.fileName, ".d.ts") ? NodeFlags.DeclarationFile : 0; sourceFile.languageVariant = getLanguageVariant(sourceFile.fileName); + scriptKind = scriptKind || ScriptKind.Unknown; + sourceFile.scriptKind = scriptKind !== ScriptKind.Unknown ? scriptKind : ScriptKind.TS; + return sourceFile; } @@ -6240,7 +6242,7 @@ namespace ts { } namespace IncrementalParser { - export function updateSourceFile(sourceFile: SourceFile, newText: string, textChangeRange: TextChangeRange, aggressiveChecks: boolean, scriptKind?: ScriptKind): SourceFile { + export function updateSourceFile(sourceFile: SourceFile, newText: string, textChangeRange: TextChangeRange, aggressiveChecks: boolean): SourceFile { aggressiveChecks = aggressiveChecks || Debug.shouldAssert(AssertionLevel.Aggressive); checkChangeRange(sourceFile, newText, textChangeRange, aggressiveChecks); @@ -6252,7 +6254,7 @@ namespace ts { if (sourceFile.statements.length === 0) { // If we don't have any statements in the current source file, then there's no real // way to incrementally parse. So just do a full parse instead. - return Parser.parseSourceFile(sourceFile.fileName, newText, sourceFile.languageVersion, /*syntaxCursor*/ undefined, /*setParentNodes*/ true, scriptKind); + return Parser.parseSourceFile(sourceFile.fileName, newText, sourceFile.languageVersion, /*syntaxCursor*/ undefined, /*setParentNodes*/ true, sourceFile.scriptKind); } // Make sure we're not trying to incrementally update a source file more than once. Once @@ -6316,7 +6318,7 @@ namespace ts { // inconsistent tree. Setting the parents on the new tree should be very fast. We // will immediately bail out of walking any subtrees when we can see that their parents // are already correct. - const result = Parser.parseSourceFile(sourceFile.fileName, newText, sourceFile.languageVersion, syntaxCursor, /*setParentNodes*/ true, scriptKind); + const result = Parser.parseSourceFile(sourceFile.fileName, newText, sourceFile.languageVersion, syntaxCursor, /*setParentNodes*/ true, sourceFile.scriptKind); return result; } diff --git a/src/compiler/types.ts b/src/compiler/types.ts index e76d4b01b54..666e20a6d29 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -1560,6 +1560,7 @@ namespace ts { hasNoDefaultLib: boolean; languageVersion: ScriptTarget; + /* @internal */ scriptKind: ScriptKind; // The first node that causes this file to be an external module /* @internal */ externalModuleIndicator: Node; diff --git a/src/services/services.ts b/src/services/services.ts index fdc366e1574..4f79ec14fdd 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -805,6 +805,7 @@ namespace ts { public identifierCount: number; public symbolCount: number; public version: string; + public scriptKind: ScriptKind; public languageVersion: ScriptTarget; public languageVariant: LanguageVariant; public identifiers: Map; @@ -818,8 +819,8 @@ namespace ts { super(kind, pos, end); } - public update(newText: string, textChangeRange: TextChangeRange, scriptKind?: ScriptKind): SourceFile { - return updateSourceFile(this, newText, textChangeRange, /*aggressiveChecks*/ undefined, scriptKind); + public update(newText: string, textChangeRange: TextChangeRange): SourceFile { + return updateSourceFile(this, newText, textChangeRange); } public getLineAndCharacterOfPosition(position: number): LineAndCharacter { @@ -1833,7 +1834,7 @@ namespace ts { else if (this.currentFileVersion !== version) { // This is the same file, just a newer version. Incrementally parse the file. const editRange = scriptSnapshot.getChangeRange(this.currentFileScriptSnapshot); - sourceFile = updateLanguageServiceSourceFile(this.currentSourceFile, scriptSnapshot, version, editRange, /*aggressiveChecks*/ undefined, scriptKind); + sourceFile = updateLanguageServiceSourceFile(this.currentSourceFile, scriptSnapshot, version, editRange); } if (sourceFile) { @@ -1968,7 +1969,7 @@ namespace ts { export let disableIncrementalParsing = false; - export function updateLanguageServiceSourceFile(sourceFile: SourceFile, scriptSnapshot: IScriptSnapshot, version: string, textChangeRange: TextChangeRange, aggressiveChecks?: boolean, scriptKind?: ScriptKind): SourceFile { + export function updateLanguageServiceSourceFile(sourceFile: SourceFile, scriptSnapshot: IScriptSnapshot, version: string, textChangeRange: TextChangeRange, aggressiveChecks?: boolean): SourceFile { // If we were given a text change range, and our version or open-ness changed, then // incrementally parse this file. if (textChangeRange) { @@ -2002,7 +2003,7 @@ namespace ts { : (changedText + suffix); } - const newSourceFile = updateSourceFile(sourceFile, newText, textChangeRange, aggressiveChecks, scriptKind); + const newSourceFile = updateSourceFile(sourceFile, newText, textChangeRange, aggressiveChecks); setSourceFileFields(newSourceFile, scriptSnapshot, version); // after incremental parsing nameTable might not be up-to-date // drop it so it can be lazily recreated later @@ -2023,7 +2024,7 @@ namespace ts { } // Otherwise, just create a new source file. - return createLanguageServiceSourceFile(sourceFile.fileName, scriptSnapshot, sourceFile.languageVersion, version, /*setNodeParents*/ true, scriptKind); + return createLanguageServiceSourceFile(sourceFile.fileName, scriptSnapshot, sourceFile.languageVersion, version, /*setNodeParents*/ true, sourceFile.scriptKind); } export function createDocumentRegistry(useCaseSensitiveFileNames?: boolean, currentDirectory = ""): DocumentRegistry { @@ -2103,7 +2104,7 @@ namespace ts { // return it as is. if (entry.sourceFile.version !== version) { entry.sourceFile = updateLanguageServiceSourceFile(entry.sourceFile, scriptSnapshot, version, - scriptSnapshot.getChangeRange(entry.sourceFile.scriptSnapshot), /*aggressiveChecks*/ undefined, scriptKind); + scriptSnapshot.getChangeRange(entry.sourceFile.scriptSnapshot)); } } diff --git a/src/services/shims.ts b/src/services/shims.ts index 44d28aff286..c1038815f2f 100644 --- a/src/services/shims.ts +++ b/src/services/shims.ts @@ -346,10 +346,10 @@ namespace ts { } public getScriptKind(fileName: string): ScriptKind { - try { + if ("getScriptKind" in this.shimHost) { return this.shimHost.getScriptKind(fileName); } - catch (e) { + else { return ScriptKind.Unknown; } } From 1c990bd242200557e131595d445ed81ed754a231 Mon Sep 17 00:00:00 2001 From: Jason Ramsay Date: Tue, 8 Mar 2016 14:35:01 -0800 Subject: [PATCH 05/26] Applying getScriptKindFromFileName to HostFileInformation before we set the ScriptKind. If the host returns ScriptKind 'Unknown' or 'undefined' we attempt to get the correct ScriptKind based on the file name. If this cannot be determined we return the default ScriptKind 'TS' --- src/compiler/parser.ts | 51 +++++++++++++++++++++++++++------------- src/services/services.ts | 6 +++++ 2 files changed, 41 insertions(+), 16 deletions(-) diff --git a/src/compiler/parser.ts b/src/compiler/parser.ts index 1d736fcf28d..e0bf1f4c2e9 100644 --- a/src/compiler/parser.ts +++ b/src/compiler/parser.ts @@ -535,9 +535,14 @@ 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); - const isJavaScriptFile = scriptKind === ScriptKind.JS || hasJavaScriptFileExtension(fileName); - initializeState(fileName, _sourceText, languageVersion, isJavaScriptFile, _syntaxCursor); + initializeState(fileName, _sourceText, languageVersion, _syntaxCursor, scriptKind); const result = parseSourceFileWorker(fileName, languageVersion, setParentNodes, scriptKind); @@ -546,12 +551,28 @@ namespace ts { return result; } - function getLanguageVariant(fileName: string) { - // .tsx and .jsx files are treated as jsx language variant. - return fileExtensionIs(fileName, ".tsx") || fileExtensionIs(fileName, ".jsx") || fileExtensionIs(fileName, ".js") ? LanguageVariant.JSX : LanguageVariant.Standard; + function getScriptKindFromFileName(fileName: string): ScriptKind { + const ext = fileName.split(".").pop(); + 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; + } } - function initializeState(fileName: string, _sourceText: string, languageVersion: ScriptTarget, isJavaScriptFile: boolean, _syntaxCursor: IncrementalParser.SyntaxCursor) { + function getLanguageVariant(scriptKind: ScriptKind) { + // .tsx and .jsx files are treated as jsx language variant. + return scriptKind === ScriptKind.TSX || scriptKind === ScriptKind.JSX || scriptKind === ScriptKind.JS ? LanguageVariant.JSX : LanguageVariant.Standard; + } + + function initializeState(fileName: string, _sourceText: string, languageVersion: ScriptTarget, _syntaxCursor: IncrementalParser.SyntaxCursor, scriptKind: ScriptKind) { NodeConstructor = objectAllocator.getNodeConstructor(); SourceFileConstructor = objectAllocator.getSourceFileConstructor(); @@ -564,14 +585,14 @@ namespace ts { identifierCount = 0; nodeCount = 0; - contextFlags = isJavaScriptFile ? ParserContextFlags.JavaScriptFile : ParserContextFlags.None; + contextFlags = scriptKind === ScriptKind.JS || scriptKind === ScriptKind.JSX ? ParserContextFlags.JavaScriptFile : ParserContextFlags.None; parseErrorBeforeNextFinishedNode = false; // Initialize and prime the scanner before parsing the source elements. scanner.setText(sourceText); scanner.setOnError(scanError); scanner.setScriptTarget(languageVersion); - scanner.setLanguageVariant(getLanguageVariant(fileName)); + scanner.setLanguageVariant(getLanguageVariant(scriptKind)); } function clearState() { @@ -587,7 +608,7 @@ namespace ts { sourceText = undefined; } - function parseSourceFileWorker(fileName: string, languageVersion: ScriptTarget, setParentNodes: boolean, scriptKind?: ScriptKind): SourceFile { + function parseSourceFileWorker(fileName: string, languageVersion: ScriptTarget, setParentNodes: boolean, scriptKind: ScriptKind): SourceFile { sourceFile = createSourceFile(fileName, languageVersion, scriptKind); if (contextFlags & ParserContextFlags.JavaScriptFile) { @@ -658,7 +679,7 @@ namespace ts { } } - function createSourceFile(fileName: string, languageVersion: ScriptTarget, scriptKind?: ScriptKind): SourceFile { + function createSourceFile(fileName: string, languageVersion: ScriptTarget, scriptKind: ScriptKind): SourceFile { // code from createNode is inlined here so createNode won't have to deal with special case of creating source files // this is quite rare comparing to other nodes and createNode should be as fast as possible const sourceFile = new SourceFileConstructor(SyntaxKind.SourceFile, /*pos*/ 0, /* end */ sourceText.length); @@ -669,10 +690,8 @@ namespace ts { sourceFile.languageVersion = languageVersion; sourceFile.fileName = normalizePath(fileName); sourceFile.flags = fileExtensionIs(sourceFile.fileName, ".d.ts") ? NodeFlags.DeclarationFile : 0; - sourceFile.languageVariant = getLanguageVariant(sourceFile.fileName); - - scriptKind = scriptKind || ScriptKind.Unknown; - sourceFile.scriptKind = scriptKind !== ScriptKind.Unknown ? scriptKind : ScriptKind.TS; + sourceFile.languageVariant = getLanguageVariant(scriptKind); + sourceFile.scriptKind = scriptKind; return sourceFile; } @@ -5609,7 +5628,7 @@ namespace ts { } export function parseJSDocTypeExpressionForTests(content: string, start: number, length: number) { - initializeState("file.js", content, ScriptTarget.Latest, /*isJavaScriptFile*/ true, /*_syntaxCursor:*/ undefined); + initializeState("file.js", content, ScriptTarget.Latest, /*_syntaxCursor:*/ undefined, ScriptKind.JS); scanner.setText(content, start, length); token = scanner.scan(); const jsDocTypeExpression = parseJSDocTypeExpression(); @@ -5928,7 +5947,7 @@ namespace ts { } export function parseIsolatedJSDocComment(content: string, start: number, length: number) { - initializeState("file.js", content, ScriptTarget.Latest, /*isJavaScriptFile*/ true, /*_syntaxCursor:*/ undefined); + initializeState("file.js", content, ScriptTarget.Latest, /*_syntaxCursor:*/ undefined, ScriptKind.JS); sourceFile = { languageVariant: LanguageVariant.Standard, text: content }; const jsDocComment = parseJSDocCommentWorker(start, length); const diagnostics = parseDiagnostics; diff --git a/src/services/services.ts b/src/services/services.ts index 4f79ec14fdd..23db7b98a27 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -2853,6 +2853,12 @@ namespace ts { // it's source file any more, and instead defers to DocumentRegistry to get // either version 1, version 2 (or some other version) depending on what the // host says should be used. + + // We do not support the scenario where a host can modify a registered + // file's script kind, i.e. in one project some file is treated as ".ts" + // and in another as ".js" + Debug.assert(hostFileInformation.scriptKind === oldSourceFile.scriptKind, "Registered script kind (" + oldSourceFile.scriptKind + ") should match new script kind (" + hostFileInformation.scriptKind +") for file: " + fileName); + return documentRegistry.updateDocument(fileName, newSettings, hostFileInformation.scriptSnapshot, hostFileInformation.version, hostFileInformation.scriptKind); } From da6059871faac02df3a0520bcaf96598b68ac906 Mon Sep 17 00:00:00 2001 From: Jason Ramsay Date: Thu, 18 Feb 2016 16:33:01 -0800 Subject: [PATCH 06/26] Applying getScriptKindFRomFileName to HostFileInformation before we set the ScriptKind. If the host returns ScriptKind 'Unknown' or 'undefined' we attempt to get the correct ScriptKind based on the file name. If this cannot be determined we return the default ScriptKind 'TS' --- src/compiler/parser.ts | 32 ++++++++++++++++---------------- src/services/services.ts | 4 ++-- 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/src/compiler/parser.ts b/src/compiler/parser.ts index e0bf1f4c2e9..5f9ea09fd6c 100644 --- a/src/compiler/parser.ts +++ b/src/compiler/parser.ts @@ -407,6 +407,22 @@ namespace ts { return result; } + /* @internal */ + export function getScriptKindFromFileName(fileName: string): ScriptKind { + const ext = fileName.split(".").pop(); + 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'. @@ -551,22 +567,6 @@ namespace ts { return result; } - function getScriptKindFromFileName(fileName: string): ScriptKind { - const ext = fileName.split(".").pop(); - 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; - } - } - function getLanguageVariant(scriptKind: ScriptKind) { // .tsx and .jsx files are treated as jsx language variant. return scriptKind === ScriptKind.TSX || scriptKind === ScriptKind.JSX || scriptKind === ScriptKind.JS ? LanguageVariant.JSX : LanguageVariant.Standard; diff --git a/src/services/services.ts b/src/services/services.ts index 23db7b98a27..3dbd34cbedd 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -1757,7 +1757,7 @@ namespace ts { hostFileName: fileName, version: this.host.getScriptVersion(fileName), scriptSnapshot: scriptSnapshot, - scriptKind: scriptKind + scriptKind: scriptKind ? scriptKind : getScriptKindFromFileName(fileName) }; } @@ -2857,7 +2857,7 @@ namespace ts { // We do not support the scenario where a host can modify a registered // file's script kind, i.e. in one project some file is treated as ".ts" // and in another as ".js" - Debug.assert(hostFileInformation.scriptKind === oldSourceFile.scriptKind, "Registered script kind (" + oldSourceFile.scriptKind + ") should match new script kind (" + hostFileInformation.scriptKind +") for file: " + fileName); + Debug.assert(hostFileInformation.scriptKind === oldSourceFile.scriptKind, "Registered script kind (" + oldSourceFile.scriptKind + ") should match new script kind (" + hostFileInformation.scriptKind + ") for file: " + fileName); return documentRegistry.updateDocument(fileName, newSettings, hostFileInformation.scriptSnapshot, hostFileInformation.version, hostFileInformation.scriptKind); } From bdc9788ec8a9de34255227f382879b8a98dd6f37 Mon Sep 17 00:00:00 2001 From: Jason Ramsay Date: Fri, 19 Feb 2016 14:20:32 -0800 Subject: [PATCH 07/26] Change to getScriptKindFromFileName to avoid unnecessary array allocation in split --- src/compiler/parser.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/compiler/parser.ts b/src/compiler/parser.ts index 5f9ea09fd6c..17ed461d10f 100644 --- a/src/compiler/parser.ts +++ b/src/compiler/parser.ts @@ -409,15 +409,15 @@ namespace ts { /* @internal */ export function getScriptKindFromFileName(fileName: string): ScriptKind { - const ext = fileName.split(".").pop(); + const ext = fileName.substr(fileName.lastIndexOf(".")); switch (ext.toLowerCase()) { - case "js": + case ".js": return ScriptKind.JS; - case "jsx": + case ".jsx": return ScriptKind.JSX; - case "ts": + case ".ts": return ScriptKind.TS; - case "tsx": + case ".tsx": return ScriptKind.TSX; default: return ScriptKind.TS; From 8a29926d5c706516e9be4bf296dcd005b7389d2f Mon Sep 17 00:00:00 2001 From: Jason Ramsay Date: Tue, 8 Mar 2016 14:42:42 -0800 Subject: [PATCH 08/26] 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 7b85aacaa89..a0a001d7679 100644 --- a/Jakefile.js +++ b/Jakefile.js @@ -920,6 +920,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 55cc2be0b72..e1e05c0a888 100644 --- a/src/compiler/commandLineParser.ts +++ b/src/compiler/commandLineParser.ts @@ -511,6 +511,7 @@ namespace ts { return { options, fileNames: getFileNames(), + typingOptions: getTypingOptions(), errors }; @@ -575,6 +576,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 cfdcb2b930c..b00753078fd 100644 --- a/src/compiler/core.ts +++ b/src/compiler/core.ts @@ -769,6 +769,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 19112e88437..eb6c2bce814 100644 --- a/src/compiler/diagnosticMessages.json +++ b/src/compiler/diagnosticMessages.json @@ -2654,5 +2654,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 } } \ No newline at end of file diff --git a/src/compiler/parser.ts b/src/compiler/parser.ts index 17ed461d10f..281adf9c90c 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 666e20a6d29..946a68ca655 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -2449,6 +2449,13 @@ namespace ts { [option: string]: string | number | boolean; } + export interface TypingOptions { + enableAutoDiscovery?: boolean; + include?: string[]; + exclude?: string[]; + [option: string]: any; + } + export const enum ModuleKind { None = 0, CommonJS = 1, @@ -2507,6 +2514,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 3dbd34cbedd..4f7e2ba8d1e 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -7,6 +7,7 @@ /// /// /// +/// /// /// @@ -1750,14 +1751,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) }; } @@ -1823,7 +1823,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 c1038815f2f..350506924fb 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; } /// @@ -230,6 +230,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) { @@ -420,8 +422,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); } @@ -951,6 +961,7 @@ namespace ts { if (result.error) { return { options: {}, + typingOptions: {}, files: [], errors: [realizeDiagnostic(result.error, "\r\n")] }; @@ -961,6 +972,7 @@ namespace ts { return { options: configFile.options, + typingOptions: configFile.typingOptions, files: configFile.fileNames, errors: realizeDiagnostics(configFile.errors, "\r\n") }; @@ -973,6 +985,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 5dcee789243..43c4477115b 100644 --- a/src/services/tsconfig.json +++ b/src/services/tsconfig.json @@ -30,6 +30,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 2f90271ca67ef5ee73e5469dbae3e36b0903262d Mon Sep 17 00:00:00 2001 From: Jason Ramsay Date: Sun, 21 Feb 2016 21:57:37 -0800 Subject: [PATCH 09/26] Fixing lint issues caught by Travis CI build (Rules appear to be more strict - this was not caught on a local lint run) --- src/services/jsTyping.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 cc3d4d3291e2fc3c52ee960d2b423ed2c4d06ba8 Mon Sep 17 00:00:00 2001 From: Jason Ramsay Date: Tue, 8 Mar 2016 15:17:09 -0800 Subject: [PATCH 10/26] 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 | 32 +++++++++++++++++++++++++++---- src/services/jsTyping.ts | 6 +++--- 2 files changed, 31 insertions(+), 7 deletions(-) diff --git a/src/compiler/commandLineParser.ts b/src/compiler/commandLineParser.ts index e1e05c0a888..7d365c6b283 100644 --- a/src/compiler/commandLineParser.ts +++ b/src/compiler/commandLineParser.ts @@ -590,10 +590,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)); @@ -636,8 +636,8 @@ namespace ts { } } if (opt.isFilePath) { - value = normalizePath(combinePaths(basePath, value)); - if (value === "") { + value = normalizePath(combinePaths(basePath, value)); + if (value === "") { value = "."; } } @@ -654,4 +654,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 9f2e36e59d6cf5655c586833e98b56d30b0a03f9 Mon Sep 17 00:00:00 2001 From: Jason Ramsay Date: Mon, 22 Feb 2016 19:33:45 -0800 Subject: [PATCH 11/26] 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 7d365c6b283..6d6392ff848 100644 --- a/src/compiler/commandLineParser.ts +++ b/src/compiler/commandLineParser.ts @@ -656,7 +656,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 5cdb97cbfd2d6cdf5f31860a8baab02be8985a1c Mon Sep 17 00:00:00 2001 From: Jason Ramsay Date: Tue, 23 Feb 2016 10:31:56 -0800 Subject: [PATCH 12/26] 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 0de8f9eeb2b1b90bfc3a38c6ab5f1eac0f7acec8 Mon Sep 17 00:00:00 2001 From: Jason Ramsay Date: Tue, 23 Feb 2016 13:30:24 -0800 Subject: [PATCH 13/26] 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 6d6392ff848..11b9576c87a 100644 --- a/src/compiler/commandLineParser.ts +++ b/src/compiler/commandLineParser.ts @@ -477,7 +477,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 8b9e3515256a98b89cd5efdf7e378ae3d6002b99 Mon Sep 17 00:00:00 2001 From: Jason Ramsay Date: Thu, 25 Feb 2016 12:32:43 -0800 Subject: [PATCH 14/26] - 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 350506924fb..19512a49b8e 100644 --- a/src/services/shims.ts +++ b/src/services/shims.ts @@ -230,7 +230,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; } @@ -986,14 +986,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 830a10fc92864ec78f3044adc206222aea7c47dd Mon Sep 17 00:00:00 2001 From: zhengbli Date: Fri, 26 Feb 2016 14:15:07 -0800 Subject: [PATCH 15/26] 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 b00753078fd..b3c7657b872 100644 --- a/src/compiler/core.ts +++ b/src/compiler/core.ts @@ -280,6 +280,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 a7a13296e6b1ecfb170f84d34ebd3fa456cdf87e Mon Sep 17 00:00:00 2001 From: Jason Ramsay Date: Tue, 8 Mar 2016 17:40:03 -0800 Subject: [PATCH 16/26] CR feedback --- src/compiler/commandLineParser.ts | 10 +++++----- src/services/jsTyping.ts | 12 +++--------- 2 files changed, 8 insertions(+), 14 deletions(-) diff --git a/src/compiler/commandLineParser.ts b/src/compiler/commandLineParser.ts index 11b9576c87a..264b2339013 100644 --- a/src/compiler/commandLineParser.ts +++ b/src/compiler/commandLineParser.ts @@ -590,10 +590,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)); @@ -636,9 +636,9 @@ namespace ts { } } if (opt.isFilePath) { - value = normalizePath(combinePaths(basePath, value)); + value = normalizePath(combinePaths(basePath, value)); if (value === "") { - value = "."; + value = "."; } } options[opt.name] = value; @@ -655,7 +655,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 ec7302e6d889d2d87e7966270fdb58750d852254 Mon Sep 17 00:00:00 2001 From: Jason Ramsay Date: Fri, 26 Feb 2016 15:33:34 -0800 Subject: [PATCH 17/26] 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 1dc57f23c7cf6999767346f6bf11f7a4a54f37d1 Mon Sep 17 00:00:00 2001 From: Jason Ramsay Date: Mon, 29 Feb 2016 08:14:00 -0800 Subject: [PATCH 18/26] - 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 085002b0604b7795a5d2871d3ddcf76e2f89cbc6 Mon Sep 17 00:00:00 2001 From: Jason Ramsay Date: Tue, 1 Mar 2016 11:50:27 -0800 Subject: [PATCH 19/26] - 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 264b2339013..ec86d4f4288 100644 --- a/src/compiler/commandLineParser.ts +++ b/src/compiler/commandLineParser.ts @@ -477,7 +477,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; @@ -588,6 +588,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 19512a49b8e..dea8d6494ef 100644 --- a/src/services/shims.ts +++ b/src/services/shims.ts @@ -230,8 +230,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) { @@ -986,31 +985,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 1fde40957275294bbceacf33176d81d03473a9f0 Mon Sep 17 00:00:00 2001 From: Jason Ramsay Date: Tue, 1 Mar 2016 18:52:11 -0800 Subject: [PATCH 20/26] - 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 946a68ca655..563f2711050 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -2453,7 +2453,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 const 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 dea8d6494ef..e9f519f1803 100644 --- a/src/services/shims.ts +++ b/src/services/shims.ts @@ -230,7 +230,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) { @@ -985,20 +985,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 51e86d3762440524c10fbbc0bd53e6e174e7aa42 Mon Sep 17 00:00:00 2001 From: Jason Ramsay Date: Tue, 1 Mar 2016 19:06:31 -0800 Subject: [PATCH 21/26] - 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 900b0a7b1840ba82b46ccbdd5a656907b81d6374 Mon Sep 17 00:00:00 2001 From: Jason Ramsay Date: Wed, 2 Mar 2016 10:11:13 -0800 Subject: [PATCH 22/26] - 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 563f2711050..5f5cc461dbb 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -2456,7 +2456,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 e9f519f1803..bf11b18b279 100644 --- a/src/services/shims.ts +++ b/src/services/shims.ts @@ -988,16 +988,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); }); } } From e5dee7eb4bcd00692a338c079ff9d38a4ef054ee Mon Sep 17 00:00:00 2001 From: Basarat Ali Syed Date: Thu, 3 Mar 2016 10:32:07 +1100 Subject: [PATCH 23/26] fix(build) `TypingResolutionHost` interface is used by exported function `discoverTypings` --- src/services/jsTyping.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/services/jsTyping.ts b/src/services/jsTyping.ts index 22265cc1016..b78434ddaa0 100644 --- a/src/services/jsTyping.ts +++ b/src/services/jsTyping.ts @@ -6,7 +6,7 @@ /* @internal */ namespace ts.JsTyping { - interface TypingResolutionHost { + export interface TypingResolutionHost { directoryExists: (path: string) => boolean; fileExists: (fileName: string) => boolean; readFile: (path: string, encoding?: string) => string; From 77f317e27f61d16b3cb5fa21423f306878f34740 Mon Sep 17 00:00:00 2001 From: Jason Ramsay Date: Thu, 3 Mar 2016 15:17:52 -0800 Subject: [PATCH 24/26] Removing cachePath from discoverTypings and DiscoverTypingsInfo. With the move to using the packageNameToLocation map it is no longer required. --- src/compiler/types.ts | 1 - src/services/jsTyping.ts | 2 -- src/services/shims.ts | 1 - 3 files changed, 4 deletions(-) diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 5f5cc461dbb..d0cd0ae6080 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -2458,7 +2458,6 @@ namespace ts { 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 safeListPath: string; // The path used to retrieve the safe list packageNameToTypingLocation: Map; // The map of package names to their cached typing locations diff --git a/src/services/jsTyping.ts b/src/services/jsTyping.ts index b78434ddaa0..943693bf52e 100644 --- a/src/services/jsTyping.ts +++ b/src/services/jsTyping.ts @@ -30,7 +30,6 @@ 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 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 packageNameToTypingLocation is the map of package names to their cached typing locations @@ -40,7 +39,6 @@ namespace ts.JsTyping { export function discoverTypings( host: TypingResolutionHost, fileNames: string[], - cachePath: Path, projectRootPath: Path, safeListPath: Path, packageNameToTypingLocation: Map, diff --git a/src/services/shims.ts b/src/services/shims.ts index bf11b18b279..a9ef3054838 100644 --- a/src/services/shims.ts +++ b/src/services/shims.ts @@ -992,7 +992,6 @@ namespace ts { return ts.JsTyping.discoverTypings( this.host, info.fileNames, - toPath(info.cachePath, info.cachePath, getCanonicalFileName), toPath(info.projectRootPath, info.projectRootPath, getCanonicalFileName), toPath(info.safeListPath, info.safeListPath, getCanonicalFileName), info.packageNameToTypingLocation, From 11102dc771b6a2262dc6f2fc00008898b1018661 Mon Sep 17 00:00:00 2001 From: Jason Ramsay Date: Tue, 8 Mar 2016 18:38:39 -0800 Subject: [PATCH 25/26] - Adding missing diagnostic message - removing extra semi-colon --- src/compiler/diagnosticMessages.json | 5 ++++- src/services/jsTyping.ts | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/compiler/diagnosticMessages.json b/src/compiler/diagnosticMessages.json index eb6c2bce814..ce9303a85c9 100644 --- a/src/compiler/diagnosticMessages.json +++ b/src/compiler/diagnosticMessages.json @@ -2450,11 +2450,14 @@ "category": "Message", "code": 6084 }, + "Option '{0}' should have array of strings as a value.": { + "category": "Error", + "code": 6103 + }, "Do not emit 'use strict' directives in module output.": { "category": "Message", "code": 6112 }, - "Variable '{0}' implicitly has an '{1}' type.": { "category": "Error", "code": 7005 diff --git a/src/services/jsTyping.ts b/src/services/jsTyping.ts index 943693bf52e..4d4903ab59d 100644 --- a/src/services/jsTyping.ts +++ b/src/services/jsTyping.ts @@ -63,7 +63,7 @@ namespace ts.JsTyping { } else { safeList = {}; - }; + } } const filesToWatch: string[] = []; From fe9da694b2c21b41247e0e9b685a190285773729 Mon Sep 17 00:00:00 2001 From: Jason Ramsay Date: Wed, 9 Mar 2016 15:50:24 -0800 Subject: [PATCH 26/26] Fixing a whitespace issue and revving the servicesVerion --- src/compiler/commandLineParser.ts | 2 +- src/services/services.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/compiler/commandLineParser.ts b/src/compiler/commandLineParser.ts index ec86d4f4288..b3a081d6b05 100644 --- a/src/compiler/commandLineParser.ts +++ b/src/compiler/commandLineParser.ts @@ -642,7 +642,7 @@ namespace ts { value = normalizePath(combinePaths(basePath, value)); if (value === "") { value = "."; - } + } } options[opt.name] = value; } diff --git a/src/services/services.ts b/src/services/services.ts index 4f7e2ba8d1e..bdd3db708be 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -13,7 +13,7 @@ namespace ts { /** The version of the language service API */ - export const servicesVersion = "0.4"; + export const servicesVersion = "0.5"; export interface Node { getSourceFile(): SourceFile;