Make the host cache store the fileName instead of undefined for the missing host files

This commit is contained in:
Sheetal Nandi
2017-07-19 13:07:55 -07:00
parent 499fabc2c1
commit 273569f6fe
20 changed files with 81 additions and 66 deletions

View File

@@ -1161,7 +1161,7 @@ namespace ts {
}
function diagnosticName(nameArg: __String | Identifier) {
return typeof nameArg === "string" ? unescapeLeadingUnderscores(nameArg as __String) : declarationNameToString(nameArg as Identifier);
return isString(nameArg) ? unescapeLeadingUnderscores(nameArg as __String) : declarationNameToString(nameArg as Identifier);
}
function isTypeParameterSymbolDeclaredInContainer(symbol: Symbol, container: Node) {

View File

@@ -880,7 +880,7 @@ namespace ts {
*/
export function readConfigFile(fileName: string, readFile: (path: string) => string | undefined): { config?: any; error?: Diagnostic } {
const textOrDiagnostic = tryReadFile(fileName, readFile);
return typeof textOrDiagnostic === "string" ? parseConfigFileTextToJson(fileName, textOrDiagnostic) : { config: {}, error: textOrDiagnostic };
return isString(textOrDiagnostic) ? parseConfigFileTextToJson(fileName, textOrDiagnostic) : { config: {}, error: textOrDiagnostic };
}
/**
@@ -902,7 +902,7 @@ namespace ts {
*/
export function readJsonConfigFile(fileName: string, readFile: (path: string) => string | undefined): JsonSourceFile {
const textOrDiagnostic = tryReadFile(fileName, readFile);
return typeof textOrDiagnostic === "string" ? parseJsonText(fileName, textOrDiagnostic) : <JsonSourceFile>{ parseDiagnostics: [textOrDiagnostic] };
return isString(textOrDiagnostic) ? parseJsonText(fileName, textOrDiagnostic) : <JsonSourceFile>{ parseDiagnostics: [textOrDiagnostic] };
}
function tryReadFile(fileName: string, readFile: (path: string) => string | undefined): string | Diagnostic {
@@ -1106,9 +1106,9 @@ namespace ts {
if (!isDoubleQuotedString(valueExpression)) {
errors.push(createDiagnosticForNodeInSourceFile(sourceFile, valueExpression, Diagnostics.String_literal_with_double_quotes_expected));
}
reportInvalidOptionValue(option && (typeof option.type === "string" && option.type !== "string"));
reportInvalidOptionValue(option && (isString(option.type) && option.type !== "string"));
const text = (<StringLiteral>valueExpression).text;
if (option && typeof option.type !== "string") {
if (option && !isString(option.type)) {
const customOption = <CommandLineOptionOfCustomType>option;
// Validate custom option type
if (!customOption.type.has(text)) {
@@ -1179,7 +1179,7 @@ namespace ts {
function getCompilerOptionValueTypeString(option: CommandLineOption) {
return option.type === "list" ?
"Array" :
typeof option.type === "string" ? option.type : "string";
isString(option.type) ? option.type : "string";
}
function isCompilerOptionsValue(option: CommandLineOption, value: any): value is CompilerOptionsValue {
@@ -1187,7 +1187,7 @@ namespace ts {
if (option.type === "list") {
return isArray(value);
}
const expectedType = typeof option.type === "string" ? option.type : "string";
const expectedType = isString(option.type) ? option.type : "string";
return typeof value === expectedType;
}
}
@@ -1571,7 +1571,7 @@ namespace ts {
let extendedConfigPath: Path;
if (json.extends) {
if (typeof json.extends !== "string") {
if (!isString(json.extends)) {
errors.push(createCompilerDiagnostic(Diagnostics.Compiler_option_0_requires_a_value_of_type_1, "extends", "string"));
}
else {
@@ -1796,7 +1796,7 @@ namespace ts {
if (optType === "list" && isArray(value)) {
return convertJsonOptionOfListType(<CommandLineOptionOfListType>opt, value, basePath, errors);
}
else if (typeof optType !== "string") {
else if (!isString(optType)) {
return convertJsonOptionOfCustomType(<CommandLineOptionOfCustomType>opt, <string>value, errors);
}
return normalizeNonListOptionValue(opt, basePath, value);
@@ -1809,12 +1809,12 @@ namespace ts {
function normalizeOptionValue(option: CommandLineOption, basePath: string, value: any): CompilerOptionsValue {
if (option.type === "list") {
const listOption = <CommandLineOptionOfListType>option;
if (listOption.element.isFilePath || typeof listOption.element.type !== "string") {
if (listOption.element.isFilePath || !isString(listOption.element.type)) {
return <CompilerOptionsValue>filter(map(value, v => normalizeOptionValue(listOption.element, basePath, v)), v => !!v);
}
return value;
}
else if (typeof option.type !== "string") {
else if (!isString(option.type)) {
return option.type.get(value);
}
return normalizeNonListOptionValue(option, basePath, value);

View File

@@ -1201,6 +1201,13 @@ namespace ts {
return Array.isArray ? Array.isArray(value) : value instanceof Array;
}
/**
* Tests whether a value is string
*/
export function isString(text: any): text is string {
return typeof text === "string";
}
export function tryCast<TOut extends TIn, TIn = any>(value: TIn | undefined, test: (value: TIn) => value is TOut): TOut | undefined {
return value !== undefined && test(value) ? value : undefined;
}
@@ -1454,16 +1461,16 @@ namespace ts {
function compareMessageText(text1: string | DiagnosticMessageChain, text2: string | DiagnosticMessageChain): Comparison {
while (text1 && text2) {
// We still have both chains.
const string1 = typeof text1 === "string" ? text1 : text1.messageText;
const string2 = typeof text2 === "string" ? text2 : text2.messageText;
const string1 = isString(text1) ? text1 : text1.messageText;
const string2 = isString(text2) ? text2 : text2.messageText;
const res = compareValues(string1, string2);
if (res) {
return res;
}
text1 = typeof text1 === "string" ? undefined : text1.next;
text2 = typeof text2 === "string" ? undefined : text2.next;
text1 = isString(text1) ? undefined : text1.next;
text2 = isString(text2) ? undefined : text2.next;
}
if (!text1 && !text2) {

View File

@@ -81,7 +81,7 @@ namespace ts {
if (typeof value === "boolean") {
return value ? createTrue() : createFalse();
}
if (typeof value === "string") {
if (isString(value)) {
return createStringLiteral(value);
}
return createLiteralFromNode(value);
@@ -2130,7 +2130,7 @@ namespace ts {
export function createCatchClause(variableDeclaration: string | VariableDeclaration, block: Block) {
const node = <CatchClause>createSynthesizedNode(SyntaxKind.CatchClause);
node.variableDeclaration = typeof variableDeclaration === "string" ? createVariableDeclaration(variableDeclaration) : variableDeclaration;
node.variableDeclaration = isString(variableDeclaration) ? createVariableDeclaration(variableDeclaration) : variableDeclaration;
node.block = block;
return node;
}
@@ -2438,11 +2438,11 @@ namespace ts {
function asName(name: string | EntityName): EntityName;
function asName(name: string | Identifier | ThisTypeNode): Identifier | ThisTypeNode;
function asName(name: string | Identifier | BindingName | PropertyName | QualifiedName | ThisTypeNode) {
return typeof name === "string" ? createIdentifier(name) : name;
return isString(name) ? createIdentifier(name) : name;
}
function asExpression(value: string | number | Expression) {
return typeof value === "string" || typeof value === "number" ? createLiteral(value) : value;
return isString(value) || typeof value === "number" ? createLiteral(value) : value;
}
function asNodeArray<T extends Node>(array: ReadonlyArray<T> | undefined): NodeArray<T> | undefined {

View File

@@ -68,7 +68,7 @@ namespace ts {
}
const fileName = jsonContent[fieldName];
if (typeof fileName !== "string") {
if (!isString(fileName)) {
if (state.traceEnabled) {
trace(state.host, Diagnostics.Expected_type_of_0_field_in_package_json_to_be_string_got_1, fieldName, typeof fileName);
}
@@ -630,8 +630,8 @@ namespace ts {
}
if (matchedPattern) {
const matchedStar = typeof matchedPattern === "string" ? undefined : matchedText(matchedPattern, moduleName);
const matchedPatternText = typeof matchedPattern === "string" ? matchedPattern : patternText(matchedPattern);
const matchedStar = isString(matchedPattern) ? undefined : matchedText(matchedPattern, moduleName);
const matchedPatternText = isString(matchedPattern) ? matchedPattern : patternText(matchedPattern);
if (state.traceEnabled) {
trace(state.host, Diagnostics.Module_name_0_matched_pattern_1, moduleName, matchedPatternText);
}

View File

@@ -337,7 +337,7 @@ namespace ts {
}
export function flattenDiagnosticMessageText(messageText: string | DiagnosticMessageChain, newLine: string): string {
if (typeof messageText === "string") {
if (isString(messageText)) {
return messageText;
}
else {

View File

@@ -184,7 +184,7 @@ namespace ts {
function fileEventHandler(eventName: string, relativeFileName: string, baseDirPath: string) {
// When files are deleted from disk, the triggered "rename" event would have a relativefileName of "undefined"
const fileName = typeof relativeFileName !== "string"
const fileName = !isString(relativeFileName)
? undefined
: ts.getNormalizedAbsolutePath(relativeFileName, baseDirPath);
// Some applications save a working file via rename operations

View File

@@ -355,7 +355,7 @@ namespace ts {
}
export function getTextOfConstantValue(value: string | number) {
return typeof value === "string" ? '"' + escapeNonAsciiString(value) + '"' : "" + value;
return isString(value) ? '"' + escapeNonAsciiString(value) + '"' : "" + value;
}
// Add an extra underscore to identifiers that start with two underscores to avoid issues with magic names like '__proto__'

View File

@@ -391,7 +391,7 @@ namespace FourSlash {
// Entry points from fourslash.ts
public goToMarker(name: string | Marker = "") {
const marker = typeof name === "string" ? this.getMarkerByName(name) : name;
const marker = ts.isString(name) ? this.getMarkerByName(name) : name;
if (this.activeFile.fileName !== marker.fileName) {
this.openFile(marker.fileName);
}
@@ -400,7 +400,7 @@ namespace FourSlash {
if (marker.position === -1 || marker.position > content.length) {
throw new Error(`Marker "${name}" has been invalidated by unrecoverable edits to the file.`);
}
const mName = typeof name === "string" ? name : this.markerName(marker);
const mName = ts.isString(name) ? name : this.markerName(marker);
this.lastKnownMarker = mName;
this.goToPosition(marker.position);
}
@@ -1028,7 +1028,7 @@ namespace FourSlash {
public verifyNoReferences(markerNameOrRange?: string | Range) {
if (markerNameOrRange) {
if (typeof markerNameOrRange === "string") {
if (ts.isString(markerNameOrRange)) {
this.goToMarker(markerNameOrRange);
}
else {
@@ -1524,7 +1524,7 @@ namespace FourSlash {
resultString += "Diagnostics:" + Harness.IO.newLine();
const diagnostics = ts.getPreEmitDiagnostics(this.languageService.getProgram());
for (const diagnostic of diagnostics) {
if (typeof diagnostic.messageText !== "string") {
if (!ts.isString(diagnostic.messageText)) {
let chainedMessage = <ts.DiagnosticMessageChain>diagnostic.messageText;
let indentation = " ";
while (chainedMessage) {
@@ -2858,7 +2858,7 @@ namespace FourSlash {
result = this.testData.files[index];
}
}
else if (typeof indexOrName === "string") {
else if (ts.isString(indexOrName)) {
let name = <string>indexOrName;
// names are stored in the compiler with this relative path, this allows people to use goTo.file on just the fileName

View File

@@ -225,7 +225,7 @@ namespace Utils {
return JSON.stringify(file, (_, v) => isNodeOrArray(v) ? serializeNode(v) : v, " ");
function getKindName(k: number | string): string {
if (typeof k === "string") {
if (ts.isString(k)) {
return k;
}

View File

@@ -254,7 +254,7 @@ class ProjectRunner extends RunnerBase {
if (option) {
const optType = option.type;
let value = <any>testCase[name];
if (typeof optType !== "string") {
if (!ts.isString(optType)) {
const key = value.toLowerCase();
const optTypeValue = optType.get(key);
if (optTypeValue) {

View File

@@ -199,7 +199,8 @@ namespace ts {
let diags = project.getLanguageService().getSemanticDiagnostics(root.name);
assert.isTrue(fileExistsCalledForBar, "'fileExists' should be called");
assert.isTrue(diags.length === 1, "one diagnostic expected");
assert.isTrue(typeof diags[0].messageText === "string" && ((<string>diags[0].messageText).indexOf("Cannot find module") === 0), "should be 'cannot find module' message");
const messageText = diags[0].messageText;
assert.isTrue(isString(messageText) && messageText.indexOf("Cannot find module") === 0, "should be 'cannot find module' message");
fileMap.set(imported.name, imported);
fileExistsCalledForBar = false;

View File

@@ -87,7 +87,7 @@ namespace ts {
"/dev/tests/scenarios/first.json": "",
"/dev/tests/baselines/first/output.ts": ""
});
const testContents = mapEntries(testContentsJson, (k, v) => [k, typeof v === "string" ? v : JSON.stringify(v)]);
const testContents = mapEntries(testContentsJson, (k, v) => [k, isString(v) ? v : JSON.stringify(v)]);
const caseInsensitiveBasePath = "c:/dev/";
const caseInsensitiveHost = new Utils.MockParseConfigHost(caseInsensitiveBasePath, /*useCaseSensitiveFileNames*/ false, mapEntries(testContents, (key, content) => [`c:${key}`, content]));

View File

@@ -282,7 +282,7 @@ namespace ts.projectSystem {
}
function makeFile(path: string, content: {} = ""): projectSystem.FileOrFolder {
return { path, content: typeof content === "string" ? "" : JSON.stringify(content) };
return { path, content: isString(content) ? "" : JSON.stringify(content) };
}
function fileStats(nonZeroStats: Partial<server.FileStats>): server.FileStats {

View File

@@ -101,7 +101,7 @@ namespace ts.projectSystem {
}
addPostExecAction(stdout: string | string[], cb: TI.RequestCompletedAction) {
const out = typeof stdout === "string" ? stdout : createNpmPackageJsonString(stdout);
const out = isString(stdout) ? stdout : createNpmPackageJsonString(stdout);
const action: PostExecAction = {
success: !!out,
callback: cb
@@ -258,7 +258,7 @@ namespace ts.projectSystem {
}
export function isFile(s: FSEntry): s is File {
return s && typeof (<File>s).content === "string";
return s && isString((<File>s).content);
}
function invokeDirectoryWatcher(callbacks: DirectoryWatcherCallback[], getRelativeFilePath: () => string) {
@@ -413,7 +413,7 @@ namespace ts.projectSystem {
const currentEntry = this.fs.get(path);
if (currentEntry) {
if (isFile(currentEntry)) {
if (typeof fileOrFolder.content === "string") {
if (isString(fileOrFolder.content)) {
// Update file
if (currentEntry.content !== fileOrFolder.content) {
currentEntry.content = fileOrFolder.content;
@@ -426,7 +426,7 @@ namespace ts.projectSystem {
}
else {
// Folder
if (typeof fileOrFolder.content === "string") {
if (isString(fileOrFolder.content)) {
// TODO: Changing from folder => file
}
else {
@@ -453,7 +453,7 @@ namespace ts.projectSystem {
}
ensureFileOrFolder(fileOrFolder: FileOrFolder) {
if (typeof fileOrFolder.content === "string") {
if (isString(fileOrFolder.content)) {
const file = this.toFile(fileOrFolder);
Debug.assert(!this.fs.get(file.path));
const baseFolder = this.ensureFolder(getDirectoryPath(file.fullPath));

View File

@@ -342,7 +342,7 @@ namespace ts.server {
convertDiagnostic(entry: protocol.DiagnosticWithLinePosition, _fileName: string): Diagnostic {
let category: DiagnosticCategory;
for (const id in DiagnosticCategory) {
if (typeof id === "string" && entry.category === id.toLowerCase()) {
if (isString(id) && entry.category === id.toLowerCase()) {
category = (<any>DiagnosticCategory)[id];
}
}

View File

@@ -159,7 +159,7 @@ namespace ts.server {
};
export function convertFormatOptions(protocolOptions: protocol.FormatCodeSettings): FormatCodeSettings {
if (typeof protocolOptions.indentStyle === "string") {
if (isString(protocolOptions.indentStyle)) {
protocolOptions.indentStyle = indentStyle.get(protocolOptions.indentStyle.toLowerCase());
Debug.assert(protocolOptions.indentStyle !== undefined);
}
@@ -169,7 +169,7 @@ namespace ts.server {
export function convertCompilerOptions(protocolOptions: protocol.ExternalProjectCompilerOptions): CompilerOptions & protocol.CompileOnSaveMixin {
compilerOptionConverters.forEach((mappedValues, id) => {
const propertyValue = protocolOptions[id];
if (typeof propertyValue === "string") {
if (isString(propertyValue)) {
protocolOptions[id] = mappedValues.get(propertyValue.toLowerCase());
}
});
@@ -177,9 +177,7 @@ namespace ts.server {
}
export function tryConvertScriptKindName(scriptKindName: protocol.ScriptKindName | ScriptKind): ScriptKind {
return typeof scriptKindName === "string"
? convertScriptKindName(scriptKindName)
: scriptKindName;
return isString(scriptKindName) ? convertScriptKindName(scriptKindName) : scriptKindName;
}
export function convertScriptKindName(scriptKindName: protocol.ScriptKindName) {
@@ -1947,7 +1945,7 @@ namespace ts.server {
// RegExp group numbers are 1-based, but the first element in groups
// is actually the original string, so it all works out in the end.
if (typeof groupNumberOrString === "number") {
if (typeof groups[groupNumberOrString] !== "string") {
if (!isString(groups[groupNumberOrString])) {
// Specification was wrong - exclude nothing!
this.logger.info(`Incorrect RegExp specification in safelist rule ${name} - not enough groups`);
// * can't appear in a filename; escape it because it's feeding into a RegExp

View File

@@ -812,18 +812,21 @@ namespace ts {
return codefix.getSupportedErrorCodes();
}
// Either it will be file name if host doesnt have file or it will be the host's file information
type CachedHostFileInformation = HostFileInformation | string;
// Cache host information about script Should be refreshed
// at each language service public entry point, since we don't know when
// the set of scripts handled by the host changes.
class HostCache {
private fileNameToEntry: Map<HostFileInformation>;
private fileNameToEntry: Map<CachedHostFileInformation>;
private _compilationSettings: CompilerOptions;
private currentDirectory: string;
constructor(private host: LanguageServiceHost, getCanonicalFileName: (fileName: string) => string) {
// script id => script index
this.currentDirectory = host.getCurrentDirectory();
this.fileNameToEntry = createMap<HostFileInformation>();
this.fileNameToEntry = createMap<CachedHostFileInformation>();
// Initialize the list with the root file names
const rootFileNames = host.getScriptFileNames();
@@ -840,7 +843,7 @@ namespace ts {
}
private createEntry(fileName: string, path: Path) {
let entry: HostFileInformation;
let entry: CachedHostFileInformation;
const scriptSnapshot = this.host.getScriptSnapshot(fileName);
if (scriptSnapshot) {
entry = {
@@ -850,36 +853,41 @@ namespace ts {
scriptKind: getScriptKind(fileName, this.host)
};
}
else {
entry = fileName;
}
this.fileNameToEntry.set(path, entry);
return entry;
}
public getEntryByPath(path: Path): HostFileInformation {
public getEntryByPath(path: Path): CachedHostFileInformation | undefined {
return this.fileNameToEntry.get(path);
}
public containsEntryByPath(path: Path): boolean {
return this.fileNameToEntry.has(path);
public getHostFileInformation(path: Path): HostFileInformation | undefined {
const entry = this.fileNameToEntry.get(path);
return !isString(entry) ? entry : undefined;
}
public getOrCreateEntryByPath(fileName: string, path: Path): HostFileInformation {
return this.containsEntryByPath(path)
? this.getEntryByPath(path)
: this.createEntry(fileName, path);
const info = this.getEntryByPath(path) || this.createEntry(fileName, path);
return isString(info) ? undefined : info;
}
public getRootFileNames(): string[] {
return this.host.getScriptFileNames();
return arrayFrom(this.fileNameToEntry.values(), entry => {
return isString(entry) ? entry : entry.hostFileName;
});
}
public getVersion(path: Path): string {
const file = this.getEntryByPath(path);
const file = this.getHostFileInformation(path);
return file && file.version;
}
public getScriptSnapshot(path: Path): IScriptSnapshot {
const file = this.getEntryByPath(path);
const file = this.getHostFileInformation(path);
return file && file.scriptSnapshot;
}
}
@@ -1145,16 +1153,17 @@ namespace ts {
fileExists: (fileName): boolean => {
// stub missing host functionality
const path = toPath(fileName, currentDirectory, getCanonicalFileName);
return hostCache.containsEntryByPath(path) ?
!!hostCache.getEntryByPath(path) :
const entry = hostCache.getEntryByPath(path);
return entry ?
!isString(entry) :
(host.fileExists && host.fileExists(fileName));
},
readFile(fileName) {
// stub missing host functionality
const path = toPath(fileName, currentDirectory, getCanonicalFileName);
if (hostCache.containsEntryByPath(path)) {
const entry = hostCache.getEntryByPath(path);
return entry && entry.scriptSnapshot.getText(0, entry.scriptSnapshot.getLength());
const entry = hostCache.getEntryByPath(path);
if (entry) {
return isString(entry) ? undefined : entry.scriptSnapshot.getText(0, entry.scriptSnapshot.getLength());
}
return host.readFile && host.readFile(fileName);
},

View File

@@ -522,7 +522,7 @@ namespace ts {
if (logPerformance) {
const end = timestamp();
logger.log(`${actionDescription} completed in ${end - start} msec`);
if (typeof result === "string") {
if (isString(result)) {
let str = result;
if (str.length > 128) {
str = str.substring(0, 128) + "...";

View File

@@ -139,7 +139,7 @@ namespace ts {
const value = options[opt.name];
// Value should be a key of opt.type
if (typeof value === "string") {
if (isString(value)) {
// If value is not a string, this will fail
options[opt.name] = parseCustomTypeOption(opt, value, diagnostics);
}