diff --git a/issue_template.md b/issue_template.md index b5872a0f9fa22..e812fe7b74c64 100644 --- a/issue_template.md +++ b/issue_template.md @@ -8,7 +8,7 @@ ```ts // A *self-contained* demonstration of the problem follows... - +// Test this by running `tsc` on the command-line, rather than through another build tool such as Gulp, Webpack, etc. ``` **Expected behavior:** diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 5d3300ba19be8..141928efb5073 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -6892,7 +6892,7 @@ namespace ts { return globalFunctionType; case "Array": case "array": - return !node.typeArguments || !node.typeArguments.length ? createArrayType(anyType) : undefined; + return !node.typeArguments || !node.typeArguments.length ? anyArrayType : undefined; case "Promise": case "promise": return !node.typeArguments || !node.typeArguments.length ? createPromiseType(anyType) : undefined; @@ -16256,15 +16256,19 @@ namespace ts { } function checkAssertion(node: AssertionExpression) { - const exprType = getRegularTypeOfObjectLiteral(getBaseTypeOfLiteralType(checkExpression(node.expression))); + return checkAssertionWorker(node, node.type, node.expression); + } - checkSourceElement(node.type); - const targetType = getTypeFromTypeNode(node.type); + function checkAssertionWorker(errNode: Node, type: TypeNode, expression: UnaryExpression | Expression, checkMode?: CheckMode) { + const exprType = getRegularTypeOfObjectLiteral(getBaseTypeOfLiteralType(checkExpression(expression, checkMode))); + + checkSourceElement(type); + const targetType = getTypeFromTypeNode(type); if (produceDiagnostics && targetType !== unknownType) { const widenedType = getWidenedType(exprType); if (!isTypeComparableTo(targetType, widenedType)) { - checkTypeComparableTo(exprType, targetType, node, Diagnostics.Type_0_cannot_be_converted_to_type_1); + checkTypeComparableTo(exprType, targetType, errNode, Diagnostics.Type_0_cannot_be_converted_to_type_1); } } return targetType; @@ -17735,6 +17739,20 @@ namespace ts { return type; } + function checkParenthesizedExpression(node: ParenthesizedExpression, checkMode?: CheckMode): Type { + if (isInJavaScriptFile(node)) { + if (node.jsDoc) { + const typecasts = flatten(map(node.jsDoc, doc => filter(doc.tags, tag => tag.kind === SyntaxKind.JSDocTypeTag))); + if (typecasts && typecasts.length) { + // We should have already issued an error if there were multiple type jsdocs + const cast = typecasts[0] as JSDocTypeTag; + return checkAssertionWorker(cast, cast.typeExpression.type, node.expression, checkMode); + } + } + } + return checkExpression(node.expression, checkMode); + } + function checkExpressionWorker(node: Expression, checkMode: CheckMode): Type { switch (node.kind) { case SyntaxKind.Identifier: @@ -17774,7 +17792,7 @@ namespace ts { case SyntaxKind.TaggedTemplateExpression: return checkTaggedTemplateExpression(node); case SyntaxKind.ParenthesizedExpression: - return checkExpression((node).expression, checkMode); + return checkParenthesizedExpression(node, checkMode); case SyntaxKind.ClassExpression: return checkClassExpression(node); case SyntaxKind.FunctionExpression: diff --git a/src/compiler/commandLineParser.ts b/src/compiler/commandLineParser.ts index 4f1210793bfc5..ac2b88fbe932c 100644 --- a/src/compiler/commandLineParser.ts +++ b/src/compiler/commandLineParser.ts @@ -752,7 +752,7 @@ namespace ts { } } - export function parseCommandLine(commandLine: string[], readFile?: (path: string) => string): ParsedCommandLine { + export function parseCommandLine(commandLine: string[], readFile?: (path: string) => string | undefined): ParsedCommandLine { const options: CompilerOptions = {}; const fileNames: string[] = []; const errors: Diagnostic[] = []; @@ -878,15 +878,9 @@ namespace ts { * Read tsconfig.json file * @param fileName The path to the config file */ - export function readConfigFile(fileName: string, readFile: (path: string) => string): { config?: any; error?: Diagnostic } { - let text = ""; - try { - text = readFile(fileName); - } - catch (e) { - return { config: {}, error: createCompilerDiagnostic(Diagnostics.Cannot_read_file_0_Colon_1, fileName, e.message) }; - } - return parseConfigFileTextToJson(fileName, text); + 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 }; } /** @@ -906,15 +900,20 @@ namespace ts { * Read tsconfig.json file * @param fileName The path to the config file */ - export function readJsonConfigFile(fileName: string, readFile: (path: string) => string): JsonSourceFile { - let text = ""; + export function readJsonConfigFile(fileName: string, readFile: (path: string) => string | undefined): JsonSourceFile { + const textOrDiagnostic = tryReadFile(fileName, readFile); + return typeof textOrDiagnostic === "string" ? parseJsonText(fileName, textOrDiagnostic) : { parseDiagnostics: [textOrDiagnostic] }; + } + + function tryReadFile(fileName: string, readFile: (path: string) => string | undefined): string | Diagnostic { + let text: string | undefined; try { text = readFile(fileName); } catch (e) { - return { parseDiagnostics: [createCompilerDiagnostic(Diagnostics.Cannot_read_file_0_Colon_1, fileName, e.message)] }; + return createCompilerDiagnostic(Diagnostics.Cannot_read_file_0_Colon_1, fileName, e.message); } - return parseJsonText(fileName, text); + return text === undefined ? createCompilerDiagnostic(Diagnostics.The_specified_path_does_not_exist_Colon_0, fileName) : text; } function commandLineOptionsToMap(options: CommandLineOption[]) { diff --git a/src/compiler/core.ts b/src/compiler/core.ts index 9f5872540f44b..6da3a5b77aa68 100644 --- a/src/compiler/core.ts +++ b/src/compiler/core.ts @@ -780,7 +780,7 @@ namespace ts { /** * Stable sort of an array. Elements equal to each other maintain their relative position in the array. */ - export function stableSort(array: ReadonlyArray, comparer: (x: T, y: T) => Comparison = compareValues) { + export function stableSort(array: ReadonlyArray, comparer: Comparer = compareValues) { return array .map((_, i) => i) // create array of indices .sort((x, y) => comparer(array[x], array[y]) || compareValues(x, y)) // sort indices by value then position @@ -852,6 +852,8 @@ namespace ts { return result; } + export type Comparer = (a: T, b: T) => Comparison; + /** * Performs a binary search, finding the index at which 'value' occurs in 'array'. * If no such index is found, returns the 2's-complement of first index at which @@ -859,7 +861,7 @@ namespace ts { * @param array A sorted array whose first element must be no larger than number * @param number The value to be searched for in the array. */ - export function binarySearch(array: ReadonlyArray, value: T, comparer?: (v1: T, v2: T) => number, offset?: number): number { + export function binarySearch(array: ReadonlyArray, value: T, comparer?: Comparer, offset?: number): number { if (!array || array.length === 0) { return -1; } diff --git a/src/compiler/parser.ts b/src/compiler/parser.ts index 20c9559a8f452..4613f49a749e0 100644 --- a/src/compiler/parser.ts +++ b/src/compiler/parser.ts @@ -4342,7 +4342,7 @@ namespace ts { parseExpected(SyntaxKind.OpenParenToken); node.expression = allowInAnd(parseExpression); parseExpected(SyntaxKind.CloseParenToken); - return finishNode(node); + return addJSDocComment(finishNode(node)); } function parseSpreadElement(): Expression { diff --git a/src/compiler/sys.ts b/src/compiler/sys.ts index 3e3ff90c00cd4..62ce272e96c58 100644 --- a/src/compiler/sys.ts +++ b/src/compiler/sys.ts @@ -23,7 +23,7 @@ namespace ts { newLine: string; useCaseSensitiveFileNames: boolean; write(s: string): void; - readFile(path: string, encoding?: string): string; + readFile(path: string, encoding?: string): string | undefined; getFileSize?(path: string): number; writeFile(path: string, data: string, writeByteOrderMark?: boolean): void; /** @@ -97,7 +97,7 @@ namespace ts { directoryExists(path: string): boolean; createDirectory(path: string): void; resolvePath(path: string): string; - readFile(path: string): string; + readFile(path: string): string | undefined; writeFile(path: string, contents: string): void; getDirectories(path: string): string[]; readDirectory(path: string, extensions?: ReadonlyArray, basePaths?: ReadonlyArray, excludeEx?: string, includeFileEx?: string, includeDirEx?: string): string[]; @@ -204,7 +204,7 @@ namespace ts { const platform: string = _os.platform(); const useCaseSensitiveFileNames = isFileSystemCaseSensitive(); - function readFile(fileName: string, _encoding?: string): string { + function readFile(fileName: string, _encoding?: string): string | undefined { if (!fileExists(fileName)) { return undefined; } diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 6ba1591d54f4b..e252f9c320779 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -2349,7 +2349,7 @@ namespace ts { */ fileExists(path: string): boolean; - readFile(path: string): string; + readFile(path: string): string | undefined; } export interface WriteFileCallback { @@ -3856,7 +3856,7 @@ namespace ts { fileExists(fileName: string): boolean; // readFile function is used to read arbitrary text files on disk, i.e. when resolution procedure needs the content of 'package.json' // to determine location of bundled typings for node module - readFile(fileName: string): string; + readFile(fileName: string): string | undefined; trace?(s: string): void; directoryExists?(directoryName: string): boolean; realpath?(path: string): string; diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index 591baaab7069f..ab0afb1282090 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -640,7 +640,8 @@ namespace ts { const commentRanges = (node.kind === SyntaxKind.Parameter || node.kind === SyntaxKind.TypeParameter || node.kind === SyntaxKind.FunctionExpression || - node.kind === SyntaxKind.ArrowFunction) ? + node.kind === SyntaxKind.ArrowFunction || + node.kind === SyntaxKind.ParenthesizedExpression) ? concatenate(getTrailingCommentRanges(text, node.pos), getLeadingCommentRanges(text, node.pos)) : getLeadingCommentRangesOfNodeFromText(node, text); // True if the comment starts with '/**' but not if it is '/**/' @@ -3925,7 +3926,7 @@ namespace ts { */ export function validateLocaleAndSetLanguage( locale: string, - sys: { getExecutingFilePath(): string, resolvePath(path: string): string, fileExists(fileName: string): boolean, readFile(fileName: string): string }, + sys: { getExecutingFilePath(): string, resolvePath(path: string): string, fileExists(fileName: string): boolean, readFile(fileName: string): string | undefined }, errors?: Diagnostic[]) { const matchResult = /^([a-z]+)([_\-]([a-z]+))?$/.exec(locale.toLowerCase()); diff --git a/src/harness/harness.ts b/src/harness/harness.ts index d5218695f09aa..55a8f3ebb4d71 100644 --- a/src/harness/harness.ts +++ b/src/harness/harness.ts @@ -479,7 +479,7 @@ namespace Harness { getCurrentDirectory(): string; useCaseSensitiveFileNames(): boolean; resolvePath(path: string): string; - readFile(path: string): string; + readFile(path: string): string | undefined; writeFile(path: string, contents: string): void; directoryName(path: string): string; getDirectories(path: string): string[]; @@ -719,7 +719,7 @@ namespace Harness { } }); - export function readFile(file: string) { + export function readFile(file: string): string | undefined { const response = Http.getFileFromServerSync(serverRoot + file); if (response.status === 200) { return response.responseText; @@ -976,7 +976,7 @@ namespace Harness { useCaseSensitiveFileNames: () => useCaseSensitiveFileNames, getNewLine: () => newLine, fileExists: fileName => fileMap.has(toPath(fileName)), - readFile: (fileName: string): string => { + readFile(fileName: string): string | undefined { const file = fileMap.get(toPath(fileName)); if (ts.endsWith(fileName, "json")) { // strip comments diff --git a/src/harness/harnessLanguageService.ts b/src/harness/harnessLanguageService.ts index 339138ba30f91..156a9e08dced0 100644 --- a/src/harness/harnessLanguageService.ts +++ b/src/harness/harnessLanguageService.ts @@ -215,7 +215,7 @@ namespace Harness.LanguageService { depth, (p) => this.virtualFileSystem.getAccessibleFileSystemEntries(p)); } - readFile(path: string): string { + readFile(path: string): string | undefined { const snapshot = this.getScriptSnapshot(path); return snapshot.getText(0, snapshot.getLength()); } @@ -619,7 +619,7 @@ namespace Harness.LanguageService { this.writeMessage(message); } - readFile(fileName: string): string { + readFile(fileName: string): string | undefined { if (fileName.indexOf(Harness.Compiler.defaultLibFileName) >= 0) { fileName = Harness.Compiler.defaultLibFileName; } diff --git a/src/harness/projectsRunner.ts b/src/harness/projectsRunner.ts index 46c2b42f6a0b7..7344c432b97ff 100644 --- a/src/harness/projectsRunner.ts +++ b/src/harness/projectsRunner.ts @@ -289,7 +289,7 @@ class ProjectRunner extends RunnerBase { return Harness.IO.fileExists(getFileNameInTheProjectTest(fileName)); } - function readFile(fileName: string): string { + function readFile(fileName: string): string | undefined { return Harness.IO.readFile(getFileNameInTheProjectTest(fileName)); } diff --git a/src/harness/unittests/moduleResolution.ts b/src/harness/unittests/moduleResolution.ts index 3923e4b2755cb..ffae1b5f4a932 100644 --- a/src/harness/unittests/moduleResolution.ts +++ b/src/harness/unittests/moduleResolution.ts @@ -56,7 +56,7 @@ namespace ts { else { return { readFile, fileExists: path => map.has(path) }; } - function readFile(path: string): string { + function readFile(path: string): string | undefined { const file = map.get(path); return file && file.content; } diff --git a/src/harness/unittests/session.ts b/src/harness/unittests/session.ts index c6ec2042fae73..862ebee4b03ab 100644 --- a/src/harness/unittests/session.ts +++ b/src/harness/unittests/session.ts @@ -9,7 +9,7 @@ namespace ts.server { newLine: "\n", useCaseSensitiveFileNames: true, write(s): void { lastWrittenToHost = s; }, - readFile(): string { return void 0; }, + readFile: () => undefined, writeFile: noop, resolvePath(): string { return void 0; }, fileExists: () => false, diff --git a/src/harness/virtualFileSystem.ts b/src/harness/virtualFileSystem.ts index da7784770dff3..8accd6f621e44 100644 --- a/src/harness/virtualFileSystem.ts +++ b/src/harness/virtualFileSystem.ts @@ -209,7 +209,7 @@ namespace Utils { } } - readFile(path: string): string { + readFile(path: string): string | undefined { const value = this.traversePath(path); if (value && value.isFile()) { return value.content.content; diff --git a/src/server/builder.ts b/src/server/builder.ts index 8fd8fbf1e6543..bc86780fbbe23 100644 --- a/src/server/builder.ts +++ b/src/server/builder.ts @@ -203,57 +203,27 @@ namespace ts.server { } class ModuleBuilderFileInfo extends BuilderFileInfo { - references: ModuleBuilderFileInfo[] = []; - referencedBy: ModuleBuilderFileInfo[] = []; + references = createSortedArray(); + readonly referencedBy = createSortedArray(); scriptVersionForReferences: string; - static compareFileInfos(lf: ModuleBuilderFileInfo, rf: ModuleBuilderFileInfo): number { - const l = lf.scriptInfo.fileName; - const r = rf.scriptInfo.fileName; - return (l < r ? -1 : (l > r ? 1 : 0)); - } - - static addToReferenceList(array: ModuleBuilderFileInfo[], fileInfo: ModuleBuilderFileInfo) { - if (array.length === 0) { - array.push(fileInfo); - return; - } - - const insertIndex = binarySearch(array, fileInfo, ModuleBuilderFileInfo.compareFileInfos); - if (insertIndex < 0) { - array.splice(~insertIndex, 0, fileInfo); - } - } - - static removeFromReferenceList(array: ModuleBuilderFileInfo[], fileInfo: ModuleBuilderFileInfo) { - if (!array || array.length === 0) { - return; - } - - if (array[0] === fileInfo) { - array.splice(0, 1); - return; - } - - const removeIndex = binarySearch(array, fileInfo, ModuleBuilderFileInfo.compareFileInfos); - if (removeIndex >= 0) { - array.splice(removeIndex, 1); - } + static compareFileInfos(lf: ModuleBuilderFileInfo, rf: ModuleBuilderFileInfo): Comparison { + return compareStrings(lf.scriptInfo.fileName, rf.scriptInfo.fileName); } addReferencedBy(fileInfo: ModuleBuilderFileInfo): void { - ModuleBuilderFileInfo.addToReferenceList(this.referencedBy, fileInfo); + insertSorted(this.referencedBy, fileInfo, ModuleBuilderFileInfo.compareFileInfos); } removeReferencedBy(fileInfo: ModuleBuilderFileInfo): void { - ModuleBuilderFileInfo.removeFromReferenceList(this.referencedBy, fileInfo); + removeSorted(this.referencedBy, fileInfo, ModuleBuilderFileInfo.compareFileInfos); } removeFileReferences() { for (const reference of this.references) { reference.removeReferencedBy(this); } - this.references = []; + this.references = createSortedArray(); } } @@ -270,16 +240,13 @@ namespace ts.server { super.clear(); } - private getReferencedFileInfos(fileInfo: ModuleBuilderFileInfo): ModuleBuilderFileInfo[] { + private getReferencedFileInfos(fileInfo: ModuleBuilderFileInfo): SortedArray { if (!fileInfo.isExternalModuleOrHasOnlyAmbientExternalModules()) { - return []; + return createSortedArray(); } const referencedFilePaths = this.project.getReferencedFiles(fileInfo.scriptInfo.path); - if (referencedFilePaths.length > 0) { - return map(referencedFilePaths, f => this.getOrCreateFileInfo(f)).sort(ModuleBuilderFileInfo.compareFileInfos); - } - return []; + return toSortedArray(referencedFilePaths.map(f => this.getOrCreateFileInfo(f)), ModuleBuilderFileInfo.compareFileInfos); } protected ensureFileInfoIfInProject(_scriptInfo: ScriptInfo) { @@ -319,39 +286,15 @@ namespace ts.server { const newReferences = this.getReferencedFileInfos(fileInfo); const oldReferences = fileInfo.references; - - let oldIndex = 0; - let newIndex = 0; - while (oldIndex < oldReferences.length && newIndex < newReferences.length) { - const oldReference = oldReferences[oldIndex]; - const newReference = newReferences[newIndex]; - const compare = ModuleBuilderFileInfo.compareFileInfos(oldReference, newReference); - if (compare < 0) { + enumerateInsertsAndDeletes(newReferences, oldReferences, + /*inserted*/ newReference => newReference.addReferencedBy(fileInfo), + /*deleted*/ oldReference => { // New reference is greater then current reference. That means // the current reference doesn't exist anymore after parsing. So delete // references. oldReference.removeReferencedBy(fileInfo); - oldIndex++; - } - else if (compare > 0) { - // A new reference info. Add it. - newReference.addReferencedBy(fileInfo); - newIndex++; - } - else { - // Equal. Go to next - oldIndex++; - newIndex++; - } - } - // Clean old references - for (let i = oldIndex; i < oldReferences.length; i++) { - oldReferences[i].removeReferencedBy(fileInfo); - } - // Update new references - for (let i = newIndex; i < newReferences.length; i++) { - newReferences[i].addReferencedBy(fileInfo); - } + }, + /*compare*/ ModuleBuilderFileInfo.compareFileInfos); fileInfo.references = newReferences; fileInfo.scriptVersionForReferences = fileInfo.scriptInfo.getLatestVersion(); diff --git a/src/server/lsHost.ts b/src/server/lsHost.ts index 3f1a4e054a83f..13b9505a658c0 100644 --- a/src/server/lsHost.ts +++ b/src/server/lsHost.ts @@ -217,7 +217,7 @@ namespace ts.server { return !this.project.isWatchedMissingFile(path) && this.host.fileExists(file); } - readFile(fileName: string): string { + readFile(fileName: string): string | undefined { return this.host.readFile(fileName); } diff --git a/src/server/project.ts b/src/server/project.ts index fab71474851a1..3b09fc48e8519 100644 --- a/src/server/project.ts +++ b/src/server/project.ts @@ -554,7 +554,7 @@ namespace ts.server { for (const sourceFile of this.program.getSourceFiles()) { this.extractUnresolvedImportsFromSourceFile(sourceFile, result); } - this.lastCachedUnresolvedImportsList = toSortedReadonlyArray(result); + this.lastCachedUnresolvedImportsList = toSortedArray(result); } unresolvedImports = this.lastCachedUnresolvedImportsList; @@ -783,7 +783,7 @@ namespace ts.server { // We need to use a set here since the code can contain the same import twice, // but that will only be one dependency. // To avoid invernal conversion, the key of the referencedFiles map must be of type Path - const referencedFiles = createMap(); + const referencedFiles = createMap(); if (sourceFile.imports && sourceFile.imports.length > 0) { const checker: TypeChecker = this.program.getTypeChecker(); for (const importName of sourceFile.imports) { @@ -1057,7 +1057,7 @@ namespace ts.server { } getExternalFiles(): SortedReadonlyArray { - return toSortedReadonlyArray(flatMap(this.plugins, plugin => { + return toSortedArray(flatMap(this.plugins, plugin => { if (typeof plugin.getExternalFiles !== "function") return; try { return plugin.getExternalFiles(this); diff --git a/src/server/types.ts b/src/server/types.ts index 72d6a7c6cfc3f..07b94fe827e19 100644 --- a/src/server/types.ts +++ b/src/server/types.ts @@ -20,8 +20,12 @@ declare namespace ts.server { require?(initialPath: string, moduleName: string): RequireResult; } + export interface SortedArray extends Array { + " __sortedArrayBrand": any; + } + export interface SortedReadonlyArray extends ReadonlyArray { - " __sortedReadonlyArrayBrand": any; + " __sortedArrayBrand": any; } export interface TypingInstallerRequest { diff --git a/src/server/typingsCache.ts b/src/server/typingsCache.ts index a97b12ff7a879..3ef37685da2c9 100644 --- a/src/server/typingsCache.ts +++ b/src/server/typingsCache.ts @@ -110,7 +110,7 @@ namespace ts.server { this.perProjectCache.set(projectName, { compilerOptions, typeAcquisition, - typings: toSortedReadonlyArray(newTypings), + typings: toSortedArray(newTypings), unresolvedImports, poisoned: false }); diff --git a/src/server/typingsInstaller/typingsInstaller.ts b/src/server/typingsInstaller/typingsInstaller.ts index 730257d2cccb1..b1fddfb412982 100644 --- a/src/server/typingsInstaller/typingsInstaller.ts +++ b/src/server/typingsInstaller/typingsInstaller.ts @@ -93,10 +93,10 @@ namespace ts.server.typingsInstaller { abstract readonly typesRegistry: Map; constructor( - readonly installTypingHost: InstallTypingHost, - readonly globalCachePath: string, - readonly safeListPath: Path, - readonly throttleLimit: number, + protected readonly installTypingHost: InstallTypingHost, + private readonly globalCachePath: string, + private readonly safeListPath: Path, + private readonly throttleLimit: number, protected readonly log = nullLog) { if (this.log.isEnabled()) { this.log.writeLine(`Global cache location '${globalCachePath}', safe file path '${safeListPath}'`); diff --git a/src/server/utilities.ts b/src/server/utilities.ts index a3dcd9161722e..30146c0a92809 100644 --- a/src/server/utilities.ts +++ b/src/server/utilities.ts @@ -9,7 +9,7 @@ namespace ts.server { verbose } - export const emptyArray: ReadonlyArray = []; + export const emptyArray: SortedReadonlyArray = createSortedArray(); export interface Logger { close(): void; @@ -190,39 +190,45 @@ namespace ts.server { return `/dev/null/inferredProject${counter}*`; } - export function toSortedReadonlyArray(arr: string[]): SortedReadonlyArray { - arr.sort(); - return arr; + export function createSortedArray(): SortedArray { + return [] as SortedArray; } - export function enumerateInsertsAndDeletes(a: SortedReadonlyArray, b: SortedReadonlyArray, inserted: (item: T) => void, deleted: (item: T) => void, compare?: (a: T, b: T) => Comparison) { + export function toSortedArray(arr: string[]): SortedArray; + export function toSortedArray(arr: T[], comparer: Comparer): SortedArray; + export function toSortedArray(arr: T[], comparer?: Comparer): SortedArray { + arr.sort(comparer); + return arr as SortedArray; + } + + export function enumerateInsertsAndDeletes(newItems: SortedReadonlyArray, oldItems: SortedReadonlyArray, inserted: (newItem: T) => void, deleted: (oldItem: T) => void, compare?: Comparer) { compare = compare || compareValues; - let aIndex = 0; - let bIndex = 0; - const aLen = a.length; - const bLen = b.length; - while (aIndex < aLen && bIndex < bLen) { - const aItem = a[aIndex]; - const bItem = b[bIndex]; - const compareResult = compare(aItem, bItem); + let newIndex = 0; + let oldIndex = 0; + const newLen = newItems.length; + const oldLen = oldItems.length; + while (newIndex < newLen && oldIndex < oldLen) { + const newItem = newItems[newIndex]; + const oldItem = oldItems[oldIndex]; + const compareResult = compare(newItem, oldItem); if (compareResult === Comparison.LessThan) { - inserted(aItem); - aIndex++; + inserted(newItem); + newIndex++; } else if (compareResult === Comparison.GreaterThan) { - deleted(bItem); - bIndex++; + deleted(oldItem); + oldIndex++; } else { - aIndex++; - bIndex++; + newIndex++; + oldIndex++; } } - while (aIndex < aLen) { - inserted(a[aIndex++]); + while (newIndex < newLen) { + inserted(newItems[newIndex++]); } - while (bIndex < bLen) { - deleted(b[bIndex++]); + while (oldIndex < oldLen) { + deleted(oldItems[oldIndex++]); } } @@ -273,4 +279,32 @@ namespace ts.server { } } } + + export function insertSorted(array: SortedArray, insert: T, compare: Comparer): void { + if (array.length === 0) { + array.push(insert); + return; + } + + const insertIndex = binarySearch(array, insert, compare); + if (insertIndex < 0) { + array.splice(~insertIndex, 0, insert); + } + } + + export function removeSorted(array: SortedArray, remove: T, compare: Comparer): void { + if (!array || array.length === 0) { + return; + } + + if (array[0] === remove) { + array.splice(0, 1); + return; + } + + const removeIndex = binarySearch(array, remove, compare); + if (removeIndex >= 0) { + array.splice(removeIndex, 1); + } + } } \ No newline at end of file diff --git a/src/services/importTracker.ts b/src/services/importTracker.ts index f17406b16f113..417c16909df2d 100644 --- a/src/services/importTracker.ts +++ b/src/services/importTracker.ts @@ -453,7 +453,7 @@ namespace ts.FindAllReferences { } } else { - const exportNode = getExportNode(parent); + const exportNode = getExportNode(parent, node); if (exportNode && hasModifier(exportNode, ModifierFlags.Export)) { if (isImportEqualsDeclaration(exportNode) && exportNode.moduleReference === node) { // We're at `Y` in `export import X = Y`. This is not the exported symbol, the left-hand-side is. So treat this as an import statement. @@ -558,10 +558,11 @@ namespace ts.FindAllReferences { // If a reference is a class expression, the exported node would be its parent. // If a reference is a variable declaration, the exported node would be the variable statement. - function getExportNode(parent: Node): Node | undefined { + function getExportNode(parent: Node, node: Node): Node | undefined { if (parent.kind === SyntaxKind.VariableDeclaration) { const p = parent as ts.VariableDeclaration; - return p.parent.kind === ts.SyntaxKind.CatchClause ? undefined : p.parent.parent.kind === SyntaxKind.VariableStatement ? p.parent.parent : undefined; + return p.name !== node ? undefined : + p.parent.kind === ts.SyntaxKind.CatchClause ? undefined : p.parent.parent.kind === SyntaxKind.VariableStatement ? p.parent.parent : undefined; } else { return parent; diff --git a/src/services/jsTyping.ts b/src/services/jsTyping.ts index f31276b6498de..670633eb44ba8 100644 --- a/src/services/jsTyping.ts +++ b/src/services/jsTyping.ts @@ -9,10 +9,10 @@ namespace ts.JsTyping { export interface TypingResolutionHost { - directoryExists: (path: string) => boolean; - fileExists: (fileName: string) => boolean; - readFile: (path: string, encoding?: string) => string; - readDirectory: (rootDir: string, extensions: ReadonlyArray, excludes: ReadonlyArray, includes: ReadonlyArray, depth?: number) => string[]; + directoryExists(path: string): boolean; + fileExists(fileName: string): boolean; + readFile(path: string, encoding?: string): string | undefined; + readDirectory(rootDir: string, extensions: ReadonlyArray, excludes: ReadonlyArray, includes: ReadonlyArray, depth?: number): string[]; } interface PackageJson { diff --git a/src/services/refactors/convertFunctionToEs6Class.ts b/src/services/refactors/convertFunctionToEs6Class.ts index a570ef1f2b86e..2fb67ea98e84f 100644 --- a/src/services/refactors/convertFunctionToEs6Class.ts +++ b/src/services/refactors/convertFunctionToEs6Class.ts @@ -12,7 +12,11 @@ namespace ts.refactor { registerRefactor(convertFunctionToES6Class); - function getAvailableActions(context: RefactorContext): ApplicableRefactorInfo[] { + function getAvailableActions(context: RefactorContext): ApplicableRefactorInfo[] | undefined { + if (!isInJavaScriptFile(context.file)) { + return undefined; + } + const start = context.startPosition; const node = getTokenAtPosition(context.file, start, /*includeJsDocComment*/ false); const checker = context.program.getTypeChecker(); diff --git a/src/services/services.ts b/src/services/services.ts index 5b2cf949d2a9a..30c6b88b1e2f9 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -1149,7 +1149,7 @@ namespace ts { !!hostCache.getEntryByPath(path) : (host.fileExists && host.fileExists(fileName)); }, - readFile: (fileName): string => { + readFile(fileName) { // stub missing host functionality const path = toPath(fileName, currentDirectory, getCanonicalFileName); if (hostCache.containsEntryByPath(path)) { diff --git a/src/services/shims.ts b/src/services/shims.ts index 2ee1853f18f81..03965cb5d4875 100644 --- a/src/services/shims.ts +++ b/src/services/shims.ts @@ -69,7 +69,7 @@ namespace ts { getTypeRootsVersion?(): number; readDirectory(rootDir: string, extension: string, basePaths?: string, excludeEx?: string, includeFileEx?: string, includeDirEx?: string, depth?: number): string; - readFile(path: string, encoding?: string): string; + readFile(path: string, encoding?: string): string | undefined; fileExists(path: string): boolean; getModuleResolutionsForFile?(fileName: string): string; @@ -95,7 +95,7 @@ namespace ts { /** * Read arbitary text files on disk, i.e. when resolution procedure needs the content of 'package.json' to determine location of bundled typings for node modules */ - readFile(fileName: string): string; + readFile(fileName: string): string | undefined; realpath?(path: string): string; trace(s: string): void; useCaseSensitiveFileNames?(): boolean; @@ -458,7 +458,7 @@ namespace ts { )); } - public readFile(path: string, encoding?: string): string { + public readFile(path: string, encoding?: string): string | undefined { return this.shimHost.readFile(path, encoding); } @@ -501,7 +501,7 @@ namespace ts { return this.shimHost.fileExists(fileName); } - public readFile(fileName: string): string { + public readFile(fileName: string): string | undefined { return this.shimHost.readFile(fileName); } @@ -1006,7 +1006,7 @@ namespace ts { class CoreServicesShimObject extends ShimBase implements CoreServicesShim { private logPerformance = false; - constructor(factory: ShimFactory, public logger: Logger, private host: CoreServicesShimHostAdapter) { + constructor(factory: ShimFactory, public readonly logger: Logger, private readonly host: CoreServicesShimHostAdapter) { super(factory); } diff --git a/src/services/types.ts b/src/services/types.ts index bfb8a80b76913..19d2345d96820 100644 --- a/src/services/types.ts +++ b/src/services/types.ts @@ -165,7 +165,7 @@ namespace ts { * Without these methods, only completions for ambient modules will be provided. */ readDirectory?(path: string, extensions?: ReadonlyArray, exclude?: ReadonlyArray, include?: ReadonlyArray, depth?: number): string[]; - readFile?(path: string, encoding?: string): string; + readFile?(path: string, encoding?: string): string | undefined; fileExists?(path: string): boolean; /* diff --git a/tests/baselines/reference/jsdocTypeTagCast.errors.txt b/tests/baselines/reference/jsdocTypeTagCast.errors.txt new file mode 100644 index 0000000000000..07a7bc94bc7f0 --- /dev/null +++ b/tests/baselines/reference/jsdocTypeTagCast.errors.txt @@ -0,0 +1,108 @@ +tests/cases/conformance/jsdoc/b.js(4,13): error TS2352: Type 'number' cannot be converted to type 'string'. +tests/cases/conformance/jsdoc/b.js(39,16): error TS2352: Type 'SomeOther' cannot be converted to type 'SomeBase'. + Property 'p' is missing in type 'SomeOther'. +tests/cases/conformance/jsdoc/b.js(43,19): error TS2352: Type 'SomeOther' cannot be converted to type 'SomeDerived'. + Property 'x' is missing in type 'SomeOther'. +tests/cases/conformance/jsdoc/b.js(45,17): error TS2352: Type 'SomeDerived' cannot be converted to type 'SomeOther'. + Property 'q' is missing in type 'SomeDerived'. +tests/cases/conformance/jsdoc/b.js(46,17): error TS2352: Type 'SomeBase' cannot be converted to type 'SomeOther'. + Property 'q' is missing in type 'SomeBase'. +tests/cases/conformance/jsdoc/b.js(54,8): error TS2352: Type 'boolean' cannot be converted to type 'string | number'. +tests/cases/conformance/jsdoc/b.js(54,15): error TS2304: Cannot find name 'numOrStr'. +tests/cases/conformance/jsdoc/b.js(54,24): error TS1005: '}' expected. +tests/cases/conformance/jsdoc/b.js(54,38): error TS2454: Variable 'numOrStr' is used before being assigned. +tests/cases/conformance/jsdoc/b.js(55,2): error TS2322: Type 'string | number' is not assignable to type 'string'. + Type 'number' is not assignable to type 'string'. +tests/cases/conformance/jsdoc/b.js(55,8): error TS2454: Variable 'numOrStr' is used before being assigned. + + +==== tests/cases/conformance/jsdoc/a.ts (0 errors) ==== + var W: string; + +==== tests/cases/conformance/jsdoc/b.js (11 errors) ==== + // @ts-check + var W = /** @type {string} */(/** @type {*} */ (4)); + + var W = /** @type {string} */(4); // Error + ~~~~~~~~~~~~~~ +!!! error TS2352: Type 'number' cannot be converted to type 'string'. + + /** @type {*} */ + var a; + + /** @type {string} */ + var s; + + var a = /** @type {*} */("" + 4); + var s = "" + /** @type {*} */(4); + + class SomeBase { + constructor() { + this.p = 42; + } + } + class SomeDerived extends SomeBase { + constructor() { + super(); + this.x = 42; + } + } + class SomeOther { + constructor() { + this.q = 42; + } + } + + // Type assertion should check for assignability in either direction + var someBase = new SomeBase(); + var someDerived = new SomeDerived(); + var someOther = new SomeOther(); + + someBase = /** @type {SomeBase} */(someDerived); + someBase = /** @type {SomeBase} */(someBase); + someBase = /** @type {SomeBase} */(someOther); // Error + ~~~~~~~~~~~~~~~~ +!!! error TS2352: Type 'SomeOther' cannot be converted to type 'SomeBase'. +!!! error TS2352: Property 'p' is missing in type 'SomeOther'. + + someDerived = /** @type {SomeDerived} */(someDerived); + someDerived = /** @type {SomeDerived} */(someBase); + someDerived = /** @type {SomeDerived} */(someOther); // Error + ~~~~~~~~~~~~~~~~~~~ +!!! error TS2352: Type 'SomeOther' cannot be converted to type 'SomeDerived'. +!!! error TS2352: Property 'x' is missing in type 'SomeOther'. + + someOther = /** @type {SomeOther} */(someDerived); // Error + ~~~~~~~~~~~~~~~~~ +!!! error TS2352: Type 'SomeDerived' cannot be converted to type 'SomeOther'. +!!! error TS2352: Property 'q' is missing in type 'SomeDerived'. + someOther = /** @type {SomeOther} */(someBase); // Error + ~~~~~~~~~~~~~~~~~ +!!! error TS2352: Type 'SomeBase' cannot be converted to type 'SomeOther'. +!!! error TS2352: Property 'q' is missing in type 'SomeBase'. + someOther = /** @type {SomeOther} */(someOther); + + // Type assertion cannot be a type-predicate type + /** @type {number | string} */ + var numOrStr; + /** @type {string} */ + var str; + if(/** @type {numOrStr is string} */(numOrStr === undefined)) { // Error + ~~~~~~~~~~~~~~~ +!!! error TS2352: Type 'boolean' cannot be converted to type 'string | number'. + ~~~~~~~~ +!!! error TS2304: Cannot find name 'numOrStr'. + ~~ +!!! error TS1005: '}' expected. + ~~~~~~~~ +!!! error TS2454: Variable 'numOrStr' is used before being assigned. + str = numOrStr; // Error, no narrowing occurred + ~~~ +!!! error TS2322: Type 'string | number' is not assignable to type 'string'. +!!! error TS2322: Type 'number' is not assignable to type 'string'. + ~~~~~~~~ +!!! error TS2454: Variable 'numOrStr' is used before being assigned. + } + + + \ No newline at end of file diff --git a/tests/baselines/reference/jsdocTypeTagCast.js b/tests/baselines/reference/jsdocTypeTagCast.js new file mode 100644 index 0000000000000..979ccfd704919 --- /dev/null +++ b/tests/baselines/reference/jsdocTypeTagCast.js @@ -0,0 +1,130 @@ +//// [tests/cases/conformance/jsdoc/jsdocTypeTagCast.ts] //// + +//// [a.ts] +var W: string; + +//// [b.js] +// @ts-check +var W = /** @type {string} */(/** @type {*} */ (4)); + +var W = /** @type {string} */(4); // Error + +/** @type {*} */ +var a; + +/** @type {string} */ +var s; + +var a = /** @type {*} */("" + 4); +var s = "" + /** @type {*} */(4); + +class SomeBase { + constructor() { + this.p = 42; + } +} +class SomeDerived extends SomeBase { + constructor() { + super(); + this.x = 42; + } +} +class SomeOther { + constructor() { + this.q = 42; + } +} + +// Type assertion should check for assignability in either direction +var someBase = new SomeBase(); +var someDerived = new SomeDerived(); +var someOther = new SomeOther(); + +someBase = /** @type {SomeBase} */(someDerived); +someBase = /** @type {SomeBase} */(someBase); +someBase = /** @type {SomeBase} */(someOther); // Error + +someDerived = /** @type {SomeDerived} */(someDerived); +someDerived = /** @type {SomeDerived} */(someBase); +someDerived = /** @type {SomeDerived} */(someOther); // Error + +someOther = /** @type {SomeOther} */(someDerived); // Error +someOther = /** @type {SomeOther} */(someBase); // Error +someOther = /** @type {SomeOther} */(someOther); + +// Type assertion cannot be a type-predicate type +/** @type {number | string} */ +var numOrStr; +/** @type {string} */ +var str; +if(/** @type {numOrStr is string} */(numOrStr === undefined)) { // Error + str = numOrStr; // Error, no narrowing occurred +} + + + + +//// [a.js] +var W; +//// [b.js] +var __extends = (this && this.__extends) || (function () { + var extendStatics = Object.setPrototypeOf || + ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || + function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; }; + return function (d, b) { + extendStatics(d, b); + function __() { this.constructor = d; } + d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); + }; +})(); +// @ts-check +var W = ((4)); +var W = (4); // Error +/** @type {*} */ +var a; +/** @type {string} */ +var s; +var a = ("" + 4); +var s = "" + (4); +var SomeBase = (function () { + function SomeBase() { + this.p = 42; + } + return SomeBase; +}()); +var SomeDerived = (function (_super) { + __extends(SomeDerived, _super); + function SomeDerived() { + var _this = _super.call(this) || this; + _this.x = 42; + return _this; + } + return SomeDerived; +}(SomeBase)); +var SomeOther = (function () { + function SomeOther() { + this.q = 42; + } + return SomeOther; +}()); +// Type assertion should check for assignability in either direction +var someBase = new SomeBase(); +var someDerived = new SomeDerived(); +var someOther = new SomeOther(); +someBase = (someDerived); +someBase = (someBase); +someBase = (someOther); // Error +someDerived = (someDerived); +someDerived = (someBase); +someDerived = (someOther); // Error +someOther = (someDerived); // Error +someOther = (someBase); // Error +someOther = (someOther); +// Type assertion cannot be a type-predicate type +/** @type {number | string} */ +var numOrStr; +/** @type {string} */ +var str; +if ((numOrStr === undefined)) { + str = numOrStr; // Error, no narrowing occurred +} diff --git a/tests/cases/conformance/jsdoc/jsdocTypeTagCast.ts b/tests/cases/conformance/jsdoc/jsdocTypeTagCast.ts new file mode 100644 index 0000000000000..62bc549cf2c84 --- /dev/null +++ b/tests/cases/conformance/jsdoc/jsdocTypeTagCast.ts @@ -0,0 +1,66 @@ +// @allowJS: true +// @suppressOutputPathCheck: true +// @strictNullChecks: true + +// @filename: a.ts +var W: string; + +// @filename: b.js +// @ts-check +var W = /** @type {string} */(/** @type {*} */ (4)); + +var W = /** @type {string} */(4); // Error + +/** @type {*} */ +var a; + +/** @type {string} */ +var s; + +var a = /** @type {*} */("" + 4); +var s = "" + /** @type {*} */(4); + +class SomeBase { + constructor() { + this.p = 42; + } +} +class SomeDerived extends SomeBase { + constructor() { + super(); + this.x = 42; + } +} +class SomeOther { + constructor() { + this.q = 42; + } +} + +// Type assertion should check for assignability in either direction +var someBase = new SomeBase(); +var someDerived = new SomeDerived(); +var someOther = new SomeOther(); + +someBase = /** @type {SomeBase} */(someDerived); +someBase = /** @type {SomeBase} */(someBase); +someBase = /** @type {SomeBase} */(someOther); // Error + +someDerived = /** @type {SomeDerived} */(someDerived); +someDerived = /** @type {SomeDerived} */(someBase); +someDerived = /** @type {SomeDerived} */(someOther); // Error + +someOther = /** @type {SomeOther} */(someDerived); // Error +someOther = /** @type {SomeOther} */(someBase); // Error +someOther = /** @type {SomeOther} */(someOther); + +// Type assertion cannot be a type-predicate type +/** @type {number | string} */ +var numOrStr; +/** @type {string} */ +var str; +if(/** @type {numOrStr is string} */(numOrStr === undefined)) { // Error + str = numOrStr; // Error, no narrowing occurred +} + + diff --git a/tests/cases/fourslash/findAllRefsExportConstEqualToClass.ts b/tests/cases/fourslash/findAllRefsExportConstEqualToClass.ts new file mode 100644 index 0000000000000..a5e9bd1cd7dd6 --- /dev/null +++ b/tests/cases/fourslash/findAllRefsExportConstEqualToClass.ts @@ -0,0 +1,17 @@ +/// + +// @Filename: /a.ts +////class [|{| "isWriteAccess": true, "isDefinition": true |}C|] {} +////export const [|{| "isWriteAccess": true, "isDefinition": true |}D|] = [|C|]; + +// @Filename: /b.ts +////import { [|{| "isWriteAccess": true, "isDefinition": true |}D|] } from "./a"; + +const [C0, D0, C1, D1] = test.ranges(); + +verify.singleReferenceGroup("class C", [C0, C1]); + +const d0Group = { definition: "const D: typeof C", ranges: [D0] }; +const d1Group = { definition: "import D", ranges: [D1] }; +verify.referenceGroups(D0, [d0Group, d1Group]); +verify.referenceGroups(D1, [d1Group, d0Group]);