diff --git a/src/compiler/binder.ts b/src/compiler/binder.ts index 5f4cf8442d6..e7b759ff64f 100644 --- a/src/compiler/binder.ts +++ b/src/compiler/binder.ts @@ -416,9 +416,8 @@ namespace ts { } } else { - // Exported module members are given 2 symbols: A local symbol that is classified with an ExportValue, - // ExportType, or ExportContainer flag, and an associated export symbol with all the correct flags set - // on it. There are 2 main reasons: + // Exported module members are given 2 symbols: A local symbol that is classified with an ExportValue flag, + // and an associated export symbol with all the correct flags set on it. There are 2 main reasons: // // 1. We treat locals and exports of the same name as mutually exclusive within a container. // That means the binder will issue a Duplicate Identifier error if you mix locals and exports @@ -438,10 +437,7 @@ namespace ts { (node as JSDocTypedefTag).name.kind === SyntaxKind.Identifier && ((node as JSDocTypedefTag).name as Identifier).isInJSDocNamespace; if ((!isAmbientModule(node) && (hasExportModifier || container.flags & NodeFlags.ExportContext)) || isJSDocTypedefInJSDocNamespace) { - const exportKind = - (symbolFlags & SymbolFlags.Value ? SymbolFlags.ExportValue : 0) | - (symbolFlags & SymbolFlags.Type ? SymbolFlags.ExportType : 0) | - (symbolFlags & SymbolFlags.Namespace ? SymbolFlags.ExportNamespace : 0); + const exportKind = symbolFlags & SymbolFlags.Value ? SymbolFlags.ExportValue : 0; const local = declareSymbol(container.locals, /*parent*/ undefined, node, exportKind, symbolExcludes); local.exportSymbol = declareSymbol(container.symbol.exports, container.symbol, node, symbolFlags, symbolExcludes); node.localSymbol = local; @@ -2288,7 +2284,7 @@ namespace ts { // When we create a property via 'exports.foo = bar', the 'exports.foo' property access // expression is the declaration setCommonJsModuleIndicator(node); - declareSymbol(file.symbol.exports, file.symbol, node.left, SymbolFlags.Property | SymbolFlags.Export, SymbolFlags.None); + declareSymbol(file.symbol.exports, file.symbol, node.left, SymbolFlags.Property | SymbolFlags.ExportValue, SymbolFlags.None); } function isExportsOrModuleExportsOrAlias(node: Node): boolean { @@ -2329,7 +2325,7 @@ namespace ts { // 'module.exports = expr' assignment setCommonJsModuleIndicator(node); - declareSymbol(file.symbol.exports, file.symbol, node, SymbolFlags.Property | SymbolFlags.Export | SymbolFlags.ValueModule, SymbolFlags.None); + declareSymbol(file.symbol.exports, file.symbol, node, SymbolFlags.Property | SymbolFlags.ExportValue | SymbolFlags.ValueModule, SymbolFlags.None); } function bindThisPropertyAssignment(node: BinaryExpression) { diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 5fac69156eb..6c5fcc36e4d 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -2010,7 +2010,7 @@ namespace ts { } function getAccessibleSymbolChainFromSymbolTableWorker(symbols: SymbolTable, visitedSymbolTables: SymbolTable[]): Symbol[] { - if (contains(visitedSymbolTables, symbols)) { + if (contains(visitedSymbolTables, symbols)) { return undefined; } visitedSymbolTables.push(symbols); @@ -2285,21 +2285,15 @@ namespace ts { } function symbolToString(symbol: Symbol, enclosingDeclaration?: Node, meaning?: SymbolFlags): string { - const writer = getSingleLineStringWriter(); - getSymbolDisplayBuilder().buildSymbolDisplay(symbol, writer, enclosingDeclaration, meaning); - const result = writer.string(); - releaseStringWriter(writer); - - return result; + return usingSingleLineStringWriter(writer => { + getSymbolDisplayBuilder().buildSymbolDisplay(symbol, writer, enclosingDeclaration, meaning); + }); } function signatureToString(signature: Signature, enclosingDeclaration?: Node, flags?: TypeFormatFlags, kind?: SignatureKind): string { - const writer = getSingleLineStringWriter(); - getSymbolDisplayBuilder().buildSignatureDisplay(signature, writer, enclosingDeclaration, flags, kind); - const result = writer.string(); - releaseStringWriter(writer); - - return result; + return usingSingleLineStringWriter(writer => { + getSymbolDisplayBuilder().buildSignatureDisplay(signature, writer, enclosingDeclaration, flags, kind); + }); } function typeToString(type: Type, enclosingDeclaration?: Node, flags?: TypeFormatFlags): string { @@ -2996,12 +2990,9 @@ namespace ts { } function typePredicateToString(typePredicate: TypePredicate, enclosingDeclaration?: Declaration, flags?: TypeFormatFlags): string { - const writer = getSingleLineStringWriter(); - getSymbolDisplayBuilder().buildTypePredicateDisplay(typePredicate, writer, enclosingDeclaration, flags); - const result = writer.string(); - releaseStringWriter(writer); - - return result; + return usingSingleLineStringWriter(writer => { + getSymbolDisplayBuilder().buildTypePredicateDisplay(typePredicate, writer, enclosingDeclaration, flags); + }); } function formatUnionTypes(types: Type[]): Type[] { @@ -4239,7 +4230,7 @@ namespace ts { return addOptionality(declaredType, /*optional*/ declaration.questionToken && includeOptionality); } - if ((noImplicitAny || declaration.flags & NodeFlags.JavaScriptFile) && + if ((noImplicitAny || isInJavaScriptFile(declaration)) && declaration.kind === SyntaxKind.VariableDeclaration && !isBindingPattern(declaration.name) && !(getCombinedModifierFlags(declaration) & ModifierFlags.Export) && !isInAmbientContext(declaration)) { // If --noImplicitAny is on or the declaration is in a Javascript file, @@ -4493,7 +4484,7 @@ namespace ts { if (declaration.kind === SyntaxKind.ExportAssignment) { return links.type = checkExpression((declaration).expression); } - if (declaration.flags & NodeFlags.JavaScriptFile && declaration.kind === SyntaxKind.JSDocPropertyTag && (declaration).typeExpression) { + if (isInJavaScriptFile(declaration) && declaration.kind === SyntaxKind.JSDocPropertyTag && (declaration).typeExpression) { return links.type = getTypeFromTypeNode((declaration).typeExpression.type); } // Handle variable, parameter or property @@ -4552,7 +4543,7 @@ namespace ts { const getter = getDeclarationOfKind(symbol, SyntaxKind.GetAccessor); const setter = getDeclarationOfKind(symbol, SyntaxKind.SetAccessor); - if (getter && getter.flags & NodeFlags.JavaScriptFile) { + if (getter && isInJavaScriptFile(getter)) { const jsDocType = getTypeForDeclarationFromJSDocComment(getter); if (jsDocType) { return links.type = jsDocType; @@ -6206,7 +6197,7 @@ namespace ts { } function isJSDocOptionalParameter(node: ParameterDeclaration) { - if (node.flags & NodeFlags.JavaScriptFile) { + if (isInJavaScriptFile(node)) { if (node.type && node.type.kind === SyntaxKind.JSDocOptionalType) { return true; } @@ -6892,6 +6883,7 @@ namespace ts { case "Null": return nullType; case "Object": + case "object": return anyType; case "Function": case "function": @@ -11065,8 +11057,14 @@ namespace ts { if (!links.switchTypes) { // If all case clauses specify expressions that have unit types, we return an array // of those unit types. Otherwise we return an empty array. - const types = map(switchStatement.caseBlock.clauses, getTypeOfSwitchClause); - links.switchTypes = !contains(types, undefined) ? types : emptyArray; + links.switchTypes = []; + for (const clause of switchStatement.caseBlock.clauses) { + const type = getTypeOfSwitchClause(clause); + if (type === undefined) { + return links.switchTypes = emptyArray; + } + links.switchTypes.push(type); + } } return links.switchTypes; } @@ -11875,6 +11873,8 @@ namespace ts { } function getTypeOfSymbolAtLocation(symbol: Symbol, location: Node) { + symbol = symbol.exportSymbol || symbol; + // If we have an identifier or a property access at the given location, if the location is // an dotted name expression, and if the location is not an assignment target, obtain the type // of the expression (which will reflect control flow analysis). If the expression indeed @@ -18800,7 +18800,7 @@ namespace ts { // local symbol is undefined => this declaration is non-exported. // however symbol might contain other declarations that are exported symbol = getSymbolOfNode(node); - if (!(symbol.flags & SymbolFlags.Export)) { + if (!symbol.exportSymbol) { // this is a pure local symbol (all declarations are non-exported) - no need to check anything return; } @@ -18811,11 +18811,9 @@ namespace ts { return; } - // we use SymbolFlags.ExportValue, SymbolFlags.ExportType and SymbolFlags.ExportNamespace - // to denote disjoint declarationSpaces (without making new enum type). - let exportedDeclarationSpaces = SymbolFlags.None; - let nonExportedDeclarationSpaces = SymbolFlags.None; - let defaultExportedDeclarationSpaces = SymbolFlags.None; + let exportedDeclarationSpaces = DeclarationSpaces.None; + let nonExportedDeclarationSpaces = DeclarationSpaces.None; + let defaultExportedDeclarationSpaces = DeclarationSpaces.None; for (const d of symbol.declarations) { const declarationSpaces = getDeclarationSpaces(d); const effectiveDeclarationFlags = getEffectiveDeclarationFlags(d, ModifierFlags.Export | ModifierFlags.Default); @@ -18855,24 +18853,36 @@ namespace ts { } } - function getDeclarationSpaces(d: Declaration): SymbolFlags { + const enum DeclarationSpaces { + None = 0, + ExportValue = 1 << 0, + ExportType = 1 << 1, + ExportNamespace = 1 << 2, + } + function getDeclarationSpaces(d: Declaration): DeclarationSpaces { switch (d.kind) { case SyntaxKind.InterfaceDeclaration: - return SymbolFlags.ExportType; + case SyntaxKind.TypeAliasDeclaration: + return DeclarationSpaces.ExportType; case SyntaxKind.ModuleDeclaration: return isAmbientModule(d) || getModuleInstanceState(d) !== ModuleInstanceState.NonInstantiated - ? SymbolFlags.ExportNamespace | SymbolFlags.ExportValue - : SymbolFlags.ExportNamespace; + ? DeclarationSpaces.ExportNamespace | DeclarationSpaces.ExportValue + : DeclarationSpaces.ExportNamespace; case SyntaxKind.ClassDeclaration: case SyntaxKind.EnumDeclaration: - return SymbolFlags.ExportType | SymbolFlags.ExportValue; + return DeclarationSpaces.ExportType | DeclarationSpaces.ExportValue; case SyntaxKind.ImportEqualsDeclaration: - let result: SymbolFlags = 0; + let result = DeclarationSpaces.None; const target = resolveAlias(getSymbolOfNode(d)); forEach(target.declarations, d => { result |= getDeclarationSpaces(d); }); return result; + case SyntaxKind.VariableDeclaration: + case SyntaxKind.BindingElement: + case SyntaxKind.FunctionDeclaration: + case SyntaxKind.ImportSpecifier: // https://github.com/Microsoft/TypeScript/pull/7591 + return DeclarationSpaces.ExportValue; default: - return SymbolFlags.ExportValue; + Debug.fail((ts as any).SyntaxKind[d.kind]); } } } @@ -20909,7 +20919,7 @@ namespace ts { } } - function areTypeParametersIdentical(declarations: (ClassDeclaration | InterfaceDeclaration)[], typeParameters: TypeParameter[]) { + function areTypeParametersIdentical(declarations: ReadonlyArray, typeParameters: TypeParameter[]) { const maxTypeArgumentCount = length(typeParameters); const minTypeArgumentCount = getMinTypeArgumentCount(typeParameters); @@ -22281,11 +22291,6 @@ namespace ts { } switch (location.kind) { - case SyntaxKind.SourceFile: - if (!isExternalOrCommonJsModule(location)) { - break; - } - // falls through case SyntaxKind.ModuleDeclaration: copySymbols(getSymbolOfNode(location).exports, meaning & SymbolFlags.ModuleMember); break; @@ -22337,7 +22342,7 @@ namespace ts { * @param meaning meaning of symbol to filter by before adding to symbol table */ function copySymbol(symbol: Symbol, meaning: SymbolFlags): void { - if (symbol.flags & meaning) { + if (getCombinedLocalAndExportSymbolFlags(symbol) & meaning) { const id = symbol.name; // We will copy all symbol regardless of its reserved name because // symbolsToArray will check whether the key is a reserved name and diff --git a/src/compiler/commandLineParser.ts b/src/compiler/commandLineParser.ts index 94b6540f16a..159dc27bb47 100644 --- a/src/compiler/commandLineParser.ts +++ b/src/compiler/commandLineParser.ts @@ -105,7 +105,7 @@ namespace ts { paramType: Diagnostics.KIND, showInSimplifiedHelpView: true, category: Diagnostics.Basic_Options, - description: Diagnostics.Specify_module_code_generation_Colon_commonjs_amd_system_umd_es2015_or_ESNext, + description: Diagnostics.Specify_module_code_generation_Colon_none_commonjs_amd_system_umd_es2015_or_ESNext, }, { name: "lib", @@ -2162,7 +2162,7 @@ namespace ts { * @param extensionPriority The priority of the extension. * @param context The expansion context. */ - function hasFileWithHigherPriorityExtension(file: string, literalFiles: Map, wildcardFiles: Map, extensions: string[], keyMapper: (value: string) => string) { + function hasFileWithHigherPriorityExtension(file: string, literalFiles: Map, wildcardFiles: Map, extensions: ReadonlyArray, keyMapper: (value: string) => string) { const extensionPriority = getExtensionPriority(file, extensions); const adjustedExtensionPriority = adjustExtensionPriority(extensionPriority, extensions); for (let i = ExtensionPriority.Highest; i < adjustedExtensionPriority; i++) { @@ -2184,7 +2184,7 @@ namespace ts { * @param extensionPriority The priority of the extension. * @param context The expansion context. */ - function removeWildcardFilesWithLowerPriorityExtension(file: string, wildcardFiles: Map, extensions: string[], keyMapper: (value: string) => string) { + function removeWildcardFilesWithLowerPriorityExtension(file: string, wildcardFiles: Map, extensions: ReadonlyArray, keyMapper: (value: string) => string) { const extensionPriority = getExtensionPriority(file, extensions); const nextExtensionPriority = getNextLowestExtensionPriority(extensionPriority, extensions); for (let i = nextExtensionPriority; i < extensions.length; i++) { diff --git a/src/compiler/core.ts b/src/compiler/core.ts index 05466c3cc4b..a1880b46471 100644 --- a/src/compiler/core.ts +++ b/src/compiler/core.ts @@ -53,7 +53,7 @@ namespace ts { } /* @internal */ - export function createSymbolTable(symbols?: Symbol[]): SymbolTable { + export function createSymbolTable(symbols?: ReadonlyArray): SymbolTable { const result = createMap() as SymbolTable; if (symbols) { for (const symbol of symbols) { @@ -88,7 +88,7 @@ namespace ts { class MapIterator { private data: MapLike; - private keys: string[]; + private keys: ReadonlyArray; private index = 0; private selector: (data: MapLike, key: string) => U; constructor(data: MapLike, selector: (data: MapLike, key: string) => U) { @@ -175,7 +175,7 @@ namespace ts { GreaterThan = 1 } - export function length(array: any[]) { + export function length(array: ReadonlyArray) { return array ? array.length : 0; } @@ -184,7 +184,7 @@ namespace ts { * returns a truthy value, then returns that value. * If no such value is found, the callback is applied to each element of array and undefined is returned. */ - export function forEach(array: T[] | undefined, callback: (element: T, index: number) => U | undefined): U | undefined { + export function forEach(array: ReadonlyArray | undefined, callback: (element: T, index: number) => U | undefined): U | undefined { if (array) { for (let i = 0; i < array.length; i++) { const result = callback(array[i], i); @@ -217,14 +217,14 @@ namespace ts { return undefined; } - export function zipWith(arrayA: T[], arrayB: U[], callback: (a: T, b: U, index: number) => void): void { + export function zipWith(arrayA: ReadonlyArray, arrayB: ReadonlyArray, callback: (a: T, b: U, index: number) => void): void { Debug.assert(arrayA.length === arrayB.length); for (let i = 0; i < arrayA.length; i++) { callback(arrayA[i], arrayB[i], i); } } - export function zipToMap(keys: string[], values: T[]): Map { + export function zipToMap(keys: ReadonlyArray, values: ReadonlyArray): Map { Debug.assert(keys.length === values.length); const map = createMap(); for (let i = 0; i < keys.length; ++i) { @@ -238,7 +238,7 @@ namespace ts { * returns a falsey value, then returns false. * If no such value is found, the callback is applied to each element of array and `true` is returned. */ - export function every(array: T[], callback: (element: T, index: number) => boolean): boolean { + export function every(array: ReadonlyArray, callback: (element: T, index: number) => boolean): boolean { if (array) { for (let i = 0; i < array.length; i++) { if (!callback(array[i], i)) { @@ -251,7 +251,7 @@ namespace ts { } /** Works like Array.prototype.find, returning `undefined` if no element satisfying the predicate is found. */ - export function find(array: T[], predicate: (element: T, index: number) => boolean): T | undefined { + export function find(array: ReadonlyArray, predicate: (element: T, index: number) => boolean): T | undefined { for (let i = 0; i < array.length; i++) { const value = array[i]; if (predicate(value, i)) { @@ -262,7 +262,7 @@ namespace ts { } /** Works like Array.prototype.findIndex, returning `-1` if no element satisfying the predicate is found. */ - export function findIndex(array: T[], predicate: (element: T, index: number) => boolean): number { + export function findIndex(array: ReadonlyArray, predicate: (element: T, index: number) => boolean): number { for (let i = 0; i < array.length; i++) { if (predicate(array[i], i)) { return i; @@ -275,7 +275,7 @@ namespace ts { * Returns the first truthy result of `callback`, or else fails. * This is like `forEach`, but never returns undefined. */ - export function findMap(array: T[], callback: (element: T, index: number) => U | undefined): U { + export function findMap(array: ReadonlyArray, callback: (element: T, index: number) => U | undefined): U { for (let i = 0; i < array.length; i++) { const result = callback(array[i], i); if (result) { @@ -285,7 +285,7 @@ namespace ts { Debug.fail(); } - export function contains(array: T[], value: T): boolean { + export function contains(array: ReadonlyArray, value: T): boolean { if (array) { for (const v of array) { if (v === value) { @@ -296,7 +296,7 @@ namespace ts { return false; } - export function indexOf(array: T[], value: T): number { + export function indexOf(array: ReadonlyArray, value: T): number { if (array) { for (let i = 0; i < array.length; i++) { if (array[i] === value) { @@ -307,7 +307,7 @@ namespace ts { return -1; } - export function indexOfAnyCharCode(text: string, charCodes: number[], start?: number): number { + export function indexOfAnyCharCode(text: string, charCodes: ReadonlyArray, start?: number): number { for (let i = start || 0; i < text.length; i++) { if (contains(charCodes, text.charCodeAt(i))) { return i; @@ -316,7 +316,7 @@ namespace ts { return -1; } - export function countWhere(array: T[], predicate: (x: T, i: number) => boolean): number { + export function countWhere(array: ReadonlyArray, predicate: (x: T, i: number) => boolean): number { let count = 0; if (array) { for (let i = 0; i < array.length; i++) { @@ -335,6 +335,8 @@ namespace ts { */ export function filter(array: T[], f: (x: T) => x is U): U[]; export function filter(array: T[], f: (x: T) => boolean): T[]; + export function filter(array: ReadonlyArray, f: (x: T) => x is U): ReadonlyArray; + export function filter(array: ReadonlyArray, f: (x: T) => boolean): ReadonlyArray; export function filter(array: T[], f: (x: T) => boolean): T[] { if (array) { const len = array.length; @@ -382,7 +384,7 @@ namespace ts { array.length = outIndex; } - export function map(array: T[], f: (x: T, i: number) => U): U[] { + export function map(array: ReadonlyArray, f: (x: T, i: number) => U): U[] { let result: U[]; if (array) { result = []; @@ -394,6 +396,8 @@ namespace ts { } // Maps from T to T and avoids allocation if all elements map to themselves + export function sameMap(array: T[], f: (x: T, i: number) => T): T[]; + export function sameMap(array: ReadonlyArray, f: (x: T, i: number) => T): ReadonlyArray; export function sameMap(array: T[], f: (x: T, i: number) => T): T[] { let result: T[]; if (array) { @@ -419,7 +423,7 @@ namespace ts { * * @param array The array to flatten. */ - export function flatten(array: (T | T[])[]): T[] { + export function flatten(array: ReadonlyArray>): T[] { let result: T[]; if (array) { result = []; @@ -444,7 +448,7 @@ namespace ts { * @param array The array to map. * @param mapfn The callback used to map the result into one or more values. */ - export function flatMap(array: T[] | undefined, mapfn: (x: T, i: number) => U | U[] | undefined): U[] | undefined { + export function flatMap(array: ReadonlyArray | undefined, mapfn: (x: T, i: number) => U | ReadonlyArray | undefined): U[] | undefined { let result: U[]; if (array) { result = []; @@ -463,6 +467,17 @@ namespace ts { return result; } + export function flatMapIter(iter: Iterator, mapfn: (x: T) => U[] | undefined): U[] { + const result: U[] = []; + while (true) { + const { value, done } = iter.next(); + if (done) break; + const res = mapfn(value); + if (res) result.push(...res); + } + return result; + } + /** * Maps an array. If the mapped value is an array, it is spread into the result. * Avoids allocation if all elements map to themselves. @@ -470,6 +485,8 @@ namespace ts { * @param array The array to map. * @param mapfn The callback used to map the result into one or more values. */ + export function sameFlatMap(array: T[], mapfn: (x: T, i: number) => T | ReadonlyArray): T[]; + export function sameFlatMap(array: ReadonlyArray, mapfn: (x: T, i: number) => T | ReadonlyArray): ReadonlyArray; export function sameFlatMap(array: T[], mapfn: (x: T, i: number) => T | T[]): T[] { let result: T[]; if (array) { @@ -508,7 +525,7 @@ namespace ts { * Computes the first matching span of elements and returns a tuple of the first span * and the remaining elements. */ - export function span(array: T[], f: (x: T, i: number) => boolean): [T[], T[]] { + export function span(array: ReadonlyArray, f: (x: T, i: number) => boolean): [T[], T[]] { if (array) { for (let i = 0; i < array.length; i++) { if (!f(array[i], i)) { @@ -528,7 +545,7 @@ namespace ts { * @param keyfn A callback used to select the key for an element. * @param mapfn A callback used to map a contiguous chunk of values to a single value. */ - export function spanMap(array: T[], keyfn: (x: T, i: number) => K, mapfn: (chunk: T[], key: K, start: number, end: number) => U): U[] { + export function spanMap(array: ReadonlyArray, keyfn: (x: T, i: number) => K, mapfn: (chunk: T[], key: K, start: number, end: number) => U): U[] { let result: U[]; if (array) { result = []; @@ -581,7 +598,7 @@ namespace ts { return result; } - export function some(array: T[], predicate?: (value: T) => boolean): boolean { + export function some(array: ReadonlyArray, predicate?: (value: T) => boolean): boolean { if (array) { if (predicate) { for (const v of array) { @@ -597,6 +614,8 @@ namespace ts { return false; } + export function concatenate(array1: T[], array2: T[]): T[]; + export function concatenate(array1: ReadonlyArray, array2: ReadonlyArray): ReadonlyArray; export function concatenate(array1: T[], array2: T[]): T[] { if (!some(array2)) return array1; if (!some(array1)) return array2; @@ -604,7 +623,7 @@ namespace ts { } // TODO: fixme (N^2) - add optional comparer so collection can be sorted before deduplication. - export function deduplicate(array: T[], areEqual?: (a: T, b: T) => boolean): T[] { + export function deduplicate(array: ReadonlyArray, areEqual?: (a: T, b: T) => boolean): T[] { let result: T[]; if (array) { result = []; @@ -661,6 +680,8 @@ namespace ts { /** * Compacts an array, removing any falsey elements. */ + export function compact(array: T[]): T[]; + export function compact(array: ReadonlyArray): ReadonlyArray; export function compact(array: T[]): T[] { let result: T[]; if (array) { @@ -700,10 +721,11 @@ namespace ts { return result; } - export function sum(array: any[], prop: string): number { + export function sum, K extends string>(array: T[], prop: K): number { let result = 0; for (const v of array) { - result += v[prop]; + // Note: we need the following type assertion because of GH #17069 + result += v[prop] as number; } return result; } @@ -727,7 +749,7 @@ namespace ts { * Gets the actual offset into an array for a relative offset. Negative offsets indicate a * position offset from the end of the array. */ - function toOffset(array: any[], offset: number) { + function toOffset(array: ReadonlyArray, offset: number) { return offset < 0 ? array.length + offset : offset; } @@ -741,7 +763,7 @@ namespace ts { * @param start The offset in `from` at which to start copying values. * @param end The offset in `from` at which to stop copying values (non-inclusive). */ - export function addRange(to: T[] | undefined, from: T[] | undefined, start?: number, end?: number): T[] | undefined { + export function addRange(to: T[] | undefined, from: ReadonlyArray | undefined, start?: number, end?: number): T[] | undefined { if (from === undefined) return to; if (to === undefined) return from.slice(start, end); start = start === undefined ? 0 : toOffset(from, start); @@ -758,14 +780,14 @@ namespace ts { /** * Stable sort of an array. Elements equal to each other maintain their relative position in the array. */ - export function stableSort(array: T[], comparer: (x: T, y: T) => Comparison = compareValues) { + export function stableSort(array: ReadonlyArray, comparer: (x: T, y: T) => Comparison = 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 .map(i => array[i]); // get sorted array } - export function rangeEquals(array1: T[], array2: T[], pos: number, end: number) { + export function rangeEquals(array1: ReadonlyArray, array2: ReadonlyArray, pos: number, end: number) { while (pos < end) { if (array1[pos] !== array2[pos]) { return false; @@ -779,7 +801,7 @@ namespace ts { * Returns the element at a specific offset in an array if non-empty, `undefined` otherwise. * A negative offset indicates the element should be retrieved from the end of the array. */ - export function elementAt(array: T[] | undefined, offset: number): T | undefined { + export function elementAt(array: ReadonlyArray | undefined, offset: number): T | undefined { if (array) { offset = toOffset(array, offset); if (offset < array.length) { @@ -792,21 +814,21 @@ namespace ts { /** * Returns the first element of an array if non-empty, `undefined` otherwise. */ - export function firstOrUndefined(array: T[]): T | undefined { + export function firstOrUndefined(array: ReadonlyArray): T | undefined { return elementAt(array, 0); } /** * Returns the last element of an array if non-empty, `undefined` otherwise. */ - export function lastOrUndefined(array: T[]): T | undefined { + export function lastOrUndefined(array: ReadonlyArray): T | undefined { return elementAt(array, -1); } /** * Returns the only element of an array if it contains only one element, `undefined` otherwise. */ - export function singleOrUndefined(array: T[]): T | undefined { + export function singleOrUndefined(array: ReadonlyArray): T | undefined { return array && array.length === 1 ? array[0] : undefined; @@ -816,13 +838,15 @@ namespace ts { * Returns the only element of an array if it contains only one element; otheriwse, returns the * array. */ + export function singleOrMany(array: T[]): T | T[]; + export function singleOrMany(array: ReadonlyArray): T | ReadonlyArray; export function singleOrMany(array: T[]): T | T[] { return array && array.length === 1 ? array[0] : array; } - export function replaceElement(array: T[], index: number, value: T): T[] { + export function replaceElement(array: ReadonlyArray, index: number, value: T): T[] { const result = array.slice(0); result[index] = value; return result; @@ -835,7 +859,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: T[], value: T, comparer?: (v1: T, v2: T) => number, offset?: number): number { + export function binarySearch(array: ReadonlyArray, value: T, comparer?: (v1: T, v2: T) => number, offset?: number): number { if (!array || array.length === 0) { return -1; } @@ -864,8 +888,8 @@ namespace ts { return ~low; } - export function reduceLeft(array: T[], f: (memo: U, value: T, i: number) => U, initial: U, start?: number, count?: number): U; - export function reduceLeft(array: T[], f: (memo: T, value: T, i: number) => T): T; + export function reduceLeft(array: ReadonlyArray, f: (memo: U, value: T, i: number) => U, initial: U, start?: number, count?: number): U; + export function reduceLeft(array: ReadonlyArray, f: (memo: T, value: T, i: number) => T): T; export function reduceLeft(array: T[], f: (memo: T, value: T, i: number) => T, initial?: T, start?: number, count?: number): T { if (array && array.length > 0) { const size = array.length; @@ -890,8 +914,8 @@ namespace ts { return initial; } - export function reduceRight(array: T[], f: (memo: U, value: T, i: number) => U, initial: U, start?: number, count?: number): U; - export function reduceRight(array: T[], f: (memo: T, value: T, i: number) => T): T; + export function reduceRight(array: ReadonlyArray, f: (memo: U, value: T, i: number) => U, initial: U, start?: number, count?: number): U; + export function reduceRight(array: ReadonlyArray, f: (memo: T, value: T, i: number) => T): T; export function reduceRight(array: T[], f: (memo: T, value: T, i: number) => T, initial?: T, start?: number, count?: number): T { if (array) { const size = array.length; @@ -1058,9 +1082,9 @@ namespace ts { * the same key with the given 'makeKey' function, then the element with the higher * index in the array will be the one associated with the produced key. */ - export function arrayToMap(array: T[], makeKey: (value: T) => string): Map; - export function arrayToMap(array: T[], makeKey: (value: T) => string, makeValue: (value: T) => U): Map; - export function arrayToMap(array: T[], makeKey: (value: T) => string, makeValue?: (value: T) => U): Map { + export function arrayToMap(array: ReadonlyArray, makeKey: (value: T) => string): Map; + export function arrayToMap(array: ReadonlyArray, makeKey: (value: T) => string, makeValue: (value: T) => U): Map; + export function arrayToMap(array: ReadonlyArray, makeKey: (value: T) => string, makeValue?: (value: T) => U): Map { const result = createMap(); for (const value of array) { result.set(makeKey(value), makeValue ? makeValue(value) : value); @@ -1073,9 +1097,9 @@ namespace ts { * * @param array the array of input elements. */ - export function arrayToSet(array: string[]): Map; - export function arrayToSet(array: T[], makeKey: (value: T) => string): Map; - export function arrayToSet(array: any[], makeKey?: (value: any) => string): Map { + export function arrayToSet(array: ReadonlyArray): Map; + export function arrayToSet(array: ReadonlyArray, makeKey: (value: T) => string): Map; + export function arrayToSet(array: ReadonlyArray, makeKey?: (value: any) => string): Map { return arrayToMap(array, makeKey || (s => s), () => true); } @@ -1158,7 +1182,7 @@ namespace ts { /** * Tests whether a value is an array. */ - export function isArray(value: any): value is any[] { + export function isArray(value: any): value is ReadonlyArray { return Array.isArray ? Array.isArray(value) : value instanceof Array; } @@ -1628,7 +1652,7 @@ namespace ts { return getNormalizedPathFromPathComponents(getNormalizedPathComponents(fileName, currentDirectory)); } - export function getNormalizedPathFromPathComponents(pathComponents: string[]) { + export function getNormalizedPathFromPathComponents(pathComponents: ReadonlyArray) { if (pathComponents && pathComponents.length) { return pathComponents[0] + pathComponents.slice(1).join(directorySeparator); } @@ -1830,7 +1854,7 @@ namespace ts { } /* @internal */ - export function fileExtensionIsOneOf(path: string, extensions: string[]): boolean { + export function fileExtensionIsOneOf(path: string, extensions: ReadonlyArray): boolean { for (const extension of extensions) { if (fileExtensionIs(path, extension)) { return true; @@ -1855,7 +1879,7 @@ namespace ts { const singleAsteriskRegexFragmentFiles = "([^./]|(\\.(?!min\\.js$))?)*"; const singleAsteriskRegexFragmentOther = "[^/]*"; - export function getRegularExpressionForWildcard(specs: string[], basePath: string, usage: "files" | "directories" | "exclude"): string | undefined { + export function getRegularExpressionForWildcard(specs: ReadonlyArray, basePath: string, usage: "files" | "directories" | "exclude"): string | undefined { const patterns = getRegularExpressionsForWildcards(specs, basePath, usage); if (!patterns || !patterns.length) { return undefined; @@ -1867,7 +1891,7 @@ namespace ts { return `^(${pattern})${terminator}`; } - function getRegularExpressionsForWildcards(specs: string[], basePath: string, usage: "files" | "directories" | "exclude"): string[] | undefined { + function getRegularExpressionsForWildcards(specs: ReadonlyArray, basePath: string, usage: "files" | "directories" | "exclude"): string[] | undefined { if (specs === undefined || specs.length === 0) { return undefined; } @@ -1972,21 +1996,21 @@ namespace ts { } export interface FileSystemEntries { - files: string[]; - directories: string[]; + files: ReadonlyArray; + directories: ReadonlyArray; } export interface FileMatcherPatterns { /** One pattern for each "include" spec. */ - includeFilePatterns: string[]; + includeFilePatterns: ReadonlyArray; /** One pattern matching one of any of the "include" specs. */ includeFilePattern: string; includeDirectoryPattern: string; excludePattern: string; - basePaths: string[]; + basePaths: ReadonlyArray; } - export function getFileMatcherPatterns(path: string, excludes: string[], includes: string[], useCaseSensitiveFileNames: boolean, currentDirectory: string): FileMatcherPatterns { + export function getFileMatcherPatterns(path: string, excludes: ReadonlyArray, includes: ReadonlyArray, useCaseSensitiveFileNames: boolean, currentDirectory: string): FileMatcherPatterns { path = normalizePath(path); currentDirectory = normalizePath(currentDirectory); const absolutePath = combinePaths(currentDirectory, path); @@ -2000,7 +2024,7 @@ namespace ts { }; } - export function matchFiles(path: string, extensions: string[], excludes: string[], includes: string[], useCaseSensitiveFileNames: boolean, currentDirectory: string, depth: number | undefined, getFileSystemEntries: (path: string) => FileSystemEntries): string[] { + export function matchFiles(path: string, extensions: ReadonlyArray, excludes: ReadonlyArray, includes: ReadonlyArray, useCaseSensitiveFileNames: boolean, currentDirectory: string, depth: number | undefined, getFileSystemEntries: (path: string) => FileSystemEntries): string[] { path = normalizePath(path); currentDirectory = normalizePath(currentDirectory); @@ -2020,7 +2044,7 @@ namespace ts { visitDirectory(basePath, combinePaths(currentDirectory, basePath), depth); } - return flatten(results); + return flatten(results); function visitDirectory(path: string, absolutePath: string, depth: number | undefined) { let { files, directories } = getFileSystemEntries(path); @@ -2064,7 +2088,7 @@ namespace ts { /** * Computes the unique non-wildcard base paths amongst the provided include patterns. */ - function getBasePaths(path: string, includes: string[], useCaseSensitiveFileNames: boolean) { + function getBasePaths(path: string, includes: ReadonlyArray, useCaseSensitiveFileNames: boolean) { // Storage for our results in the form of literal paths (e.g. the paths as written by the user). const basePaths: string[] = [path]; @@ -2136,13 +2160,13 @@ namespace ts { /** * List of supported extensions in order of file resolution precedence. */ - export const supportedTypeScriptExtensions = [Extension.Ts, Extension.Tsx, Extension.Dts]; + export const supportedTypeScriptExtensions: ReadonlyArray = [Extension.Ts, Extension.Tsx, Extension.Dts]; /** Must have ".d.ts" first because if ".ts" goes first, that will be detected as the extension instead of ".d.ts". */ - export const supportedTypescriptExtensionsForExtractExtension = [Extension.Dts, Extension.Ts, Extension.Tsx]; - export const supportedJavascriptExtensions = [Extension.Js, Extension.Jsx]; - const allSupportedExtensions = supportedTypeScriptExtensions.concat(supportedJavascriptExtensions); + export const supportedTypescriptExtensionsForExtractExtension: ReadonlyArray = [Extension.Dts, Extension.Ts, Extension.Tsx]; + export const supportedJavascriptExtensions: ReadonlyArray = [Extension.Js, Extension.Jsx]; + const allSupportedExtensions = [...supportedTypeScriptExtensions, ...supportedJavascriptExtensions]; - export function getSupportedExtensions(options?: CompilerOptions, extraFileExtensions?: JsFileExtensionInfo[]): string[] { + export function getSupportedExtensions(options?: CompilerOptions, extraFileExtensions?: ReadonlyArray): ReadonlyArray { const needAllExtensions = options && options.allowJs; if (!extraFileExtensions || extraFileExtensions.length === 0 || !needAllExtensions) { return needAllExtensions ? allSupportedExtensions : supportedTypeScriptExtensions; @@ -2164,7 +2188,7 @@ namespace ts { return forEach(supportedTypeScriptExtensions, extension => fileExtensionIs(fileName, extension)); } - export function isSupportedSourceFileName(fileName: string, compilerOptions?: CompilerOptions, extraFileExtensions?: JsFileExtensionInfo[]) { + export function isSupportedSourceFileName(fileName: string, compilerOptions?: CompilerOptions, extraFileExtensions?: ReadonlyArray) { if (!fileName) { return false; } for (const extension of getSupportedExtensions(compilerOptions, extraFileExtensions)) { @@ -2188,7 +2212,7 @@ namespace ts { Lowest = DeclarationAndJavaScriptFiles, } - export function getExtensionPriority(path: string, supportedExtensions: string[]): ExtensionPriority { + export function getExtensionPriority(path: string, supportedExtensions: ReadonlyArray): ExtensionPriority { for (let i = supportedExtensions.length - 1; i >= 0; i--) { if (fileExtensionIs(path, supportedExtensions[i])) { return adjustExtensionPriority(i, supportedExtensions); @@ -2203,7 +2227,7 @@ namespace ts { /** * Adjusts an extension priority to be the highest priority within the same range. */ - export function adjustExtensionPriority(extensionPriority: ExtensionPriority, supportedExtensions: string[]): ExtensionPriority { + export function adjustExtensionPriority(extensionPriority: ExtensionPriority, supportedExtensions: ReadonlyArray): ExtensionPriority { if (extensionPriority < ExtensionPriority.DeclarationAndJavaScriptFiles) { return ExtensionPriority.TypeScriptFiles; } @@ -2212,12 +2236,13 @@ namespace ts { } else { return supportedExtensions.length; - } } + } + } /** * Gets the next lowest extension priority for a given priority. */ - export function getNextLowestExtensionPriority(extensionPriority: ExtensionPriority, supportedExtensions: string[]): ExtensionPriority { + export function getNextLowestExtensionPriority(extensionPriority: ExtensionPriority, supportedExtensions: ReadonlyArray): ExtensionPriority { if (extensionPriority < ExtensionPriority.DeclarationAndJavaScriptFiles) { return ExtensionPriority.DeclarationAndJavaScriptFiles; } @@ -2406,7 +2431,7 @@ namespace ts { * (These are verified by verifyCompilerOptions to have 0 or 1 "*" characters.) */ /* @internal */ - export function matchPatternOrExact(patternStrings: string[], candidate: string): string | Pattern | undefined { + export function matchPatternOrExact(patternStrings: ReadonlyArray, candidate: string): string | Pattern | undefined { const patterns: Pattern[] = []; for (const patternString of patternStrings) { const pattern = tryParsePattern(patternString); @@ -2439,7 +2464,7 @@ namespace ts { /** Return the object corresponding to the best pattern to match `candidate`. */ /* @internal */ - export function findBestPatternMatch(values: T[], getPattern: (value: T) => Pattern, candidate: string): T | undefined { + export function findBestPatternMatch(values: ReadonlyArray, getPattern: (value: T) => Pattern, candidate: string): T | undefined { let matchedValue: T | undefined = undefined; // use length of prefix as betterness criteria let longestMatchPrefixLength = -1; @@ -2495,7 +2520,7 @@ namespace ts { Debug.fail(`File ${path} has unknown extension.`); } export function tryGetExtensionFromPath(path: string): Extension | undefined { - return find(supportedTypescriptExtensionsForExtractExtension, e => fileExtensionIs(path, e)) || find(supportedJavascriptExtensions, e => fileExtensionIs(path, e)); + return find(supportedTypescriptExtensionsForExtractExtension, e => fileExtensionIs(path, e)) || find(supportedJavascriptExtensions, e => fileExtensionIs(path, e)); } export function isCheckJsEnabledForFile(sourceFile: SourceFile, compilerOptions: CompilerOptions) { diff --git a/src/compiler/diagnosticMessages.json b/src/compiler/diagnosticMessages.json index 1fd434abf4c..cf198ec06ec 100644 --- a/src/compiler/diagnosticMessages.json +++ b/src/compiler/diagnosticMessages.json @@ -2658,7 +2658,7 @@ "category": "Message", "code": 6015 }, - "Specify module code generation: 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'.": { + "Specify module code generation: 'none', commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'.": { "category": "Message", "code": 6016 }, diff --git a/src/compiler/parser.ts b/src/compiler/parser.ts index 4a979fb3e6f..089bff53a05 100644 --- a/src/compiler/parser.ts +++ b/src/compiler/parser.ts @@ -2,6 +2,15 @@ /// namespace ts { + const enum SignatureFlags { + None = 0, + Yield = 1 << 0, + Await = 1 << 1, + Type = 1 << 2, + RequireCompleteParameterList = 1 << 3, + IgnoreMissingOpenBrace = 1 << 4, + } + let NodeConstructor: new (kind: SyntaxKind, pos: number, end: number) => Node; let TokenConstructor: new (kind: SyntaxKind, pos: number, end: number) => Node; let IdentifierConstructor: new (kind: SyntaxKind, pos: number, end: number) => Node; @@ -2218,15 +2227,12 @@ namespace ts { function fillSignature( returnToken: SyntaxKind.ColonToken | SyntaxKind.EqualsGreaterThanToken, - yieldContext: boolean, - awaitContext: boolean, - requireCompleteParameterList: boolean, + flags: SignatureFlags, signature: SignatureDeclaration): void { + signature.typeParameters = parseTypeParameters(); + signature.parameters = parseParameterList(flags); const returnTokenRequired = returnToken === SyntaxKind.EqualsGreaterThanToken; - signature.typeParameters = parseTypeParameters(); - signature.parameters = parseParameterList(yieldContext, awaitContext, requireCompleteParameterList); - if (returnTokenRequired) { parseExpected(returnToken); signature.type = parseTypeOrTypePredicate(); @@ -2234,9 +2240,19 @@ namespace ts { else if (parseOptional(returnToken)) { signature.type = parseTypeOrTypePredicate(); } + else if (flags & SignatureFlags.Type) { + const start = scanner.getTokenPos(); + const length = scanner.getTextPos() - start; + const backwardToken = parseOptional(returnToken === SyntaxKind.ColonToken ? SyntaxKind.EqualsGreaterThanToken : SyntaxKind.ColonToken); + if (backwardToken) { + // This is easy to get backward, especially in type contexts, so parse the type anyway + signature.type = parseTypeOrTypePredicate(); + parseErrorAtPosition(start, length, Diagnostics._0_expected, tokenToString(returnToken)); + } + } } - function parseParameterList(yieldContext: boolean, awaitContext: boolean, requireCompleteParameterList: boolean) { + function parseParameterList(flags: SignatureFlags) { // FormalParameters [Yield,Await]: (modified) // [empty] // FormalParameterList[?Yield,Await] @@ -2254,15 +2270,15 @@ namespace ts { const savedYieldContext = inYieldContext(); const savedAwaitContext = inAwaitContext(); - setYieldContext(yieldContext); - setAwaitContext(awaitContext); + setYieldContext(!!(flags & SignatureFlags.Yield)); + setAwaitContext(!!(flags & SignatureFlags.Await)); const result = parseDelimitedList(ParsingContext.Parameters, parseParameter); setYieldContext(savedYieldContext); setAwaitContext(savedAwaitContext); - if (!parseExpected(SyntaxKind.CloseParenToken) && requireCompleteParameterList) { + if (!parseExpected(SyntaxKind.CloseParenToken) && (flags & SignatureFlags.RequireCompleteParameterList)) { // Caller insisted that we had to end with a ) We didn't. So just return // undefined here. return undefined; @@ -2274,7 +2290,7 @@ namespace ts { // We didn't even have an open paren. If the caller requires a complete parameter list, // we definitely can't provide that. However, if they're ok with an incomplete one, // then just return an empty set of parameters. - return requireCompleteParameterList ? undefined : createMissingList(); + return (flags & SignatureFlags.RequireCompleteParameterList) ? undefined : createMissingList(); } function parseTypeMemberSemicolon() { @@ -2293,7 +2309,7 @@ namespace ts { if (kind === SyntaxKind.ConstructSignature) { parseExpected(SyntaxKind.NewKeyword); } - fillSignature(SyntaxKind.ColonToken, /*yieldContext*/ false, /*awaitContext*/ false, /*requireCompleteParameterList*/ false, node); + fillSignature(SyntaxKind.ColonToken, SignatureFlags.Type, node); parseTypeMemberSemicolon(); return addJSDocComment(finishNode(node)); } @@ -2383,7 +2399,7 @@ namespace ts { // Method signatures don't exist in expression contexts. So they have neither // [Yield] nor [Await] - fillSignature(SyntaxKind.ColonToken, /*yieldContext*/ false, /*awaitContext*/ false, /*requireCompleteParameterList*/ false, method); + fillSignature(SyntaxKind.ColonToken, SignatureFlags.Type, method); parseTypeMemberSemicolon(); return addJSDocComment(finishNode(method)); } @@ -2527,7 +2543,7 @@ namespace ts { if (kind === SyntaxKind.ConstructorType) { parseExpected(SyntaxKind.NewKeyword); } - fillSignature(SyntaxKind.EqualsGreaterThanToken, /*yieldContext*/ false, /*awaitContext*/ false, /*requireCompleteParameterList*/ false, node); + fillSignature(SyntaxKind.EqualsGreaterThanToken, SignatureFlags.Type, node); return finishNode(node); } @@ -3254,7 +3270,7 @@ namespace ts { function parseParenthesizedArrowFunctionExpressionHead(allowAmbiguity: boolean): ArrowFunction { const node = createNode(SyntaxKind.ArrowFunction); node.modifiers = parseModifiersForArrowFunction(); - const isAsync = !!(getModifierFlags(node) & ModifierFlags.Async); + const isAsync = (getModifierFlags(node) & ModifierFlags.Async) ? SignatureFlags.Await : SignatureFlags.None; // Arrow functions are never generators. // @@ -3263,7 +3279,7 @@ namespace ts { // a => (b => c) // And think that "(b =>" was actually a parenthesized arrow function with a missing // close paren. - fillSignature(SyntaxKind.ColonToken, /*yieldContext*/ false, /*awaitContext*/ isAsync, /*requireCompleteParameterList*/ !allowAmbiguity, node); + fillSignature(SyntaxKind.ColonToken, isAsync | (allowAmbiguity ? SignatureFlags.None : SignatureFlags.RequireCompleteParameterList), node); // If we couldn't get parameters, we definitely could not parse out an arrow function. if (!node.parameters) { @@ -3288,7 +3304,7 @@ namespace ts { function parseArrowFunctionExpressionBody(isAsync: boolean): Block | Expression { if (token() === SyntaxKind.OpenBraceToken) { - return parseFunctionBlock(/*allowYield*/ false, /*allowAwait*/ isAsync, /*ignoreMissingOpenBrace*/ false); + return parseFunctionBlock(isAsync ? SignatureFlags.Await : SignatureFlags.None); } if (token() !== SyntaxKind.SemicolonToken && @@ -3309,8 +3325,8 @@ namespace ts { // try to recover better. If we don't do this, then the next close curly we see may end // up preemptively closing the containing construct. // - // Note: even when 'ignoreMissingOpenBrace' is passed as true, parseBody will still error. - return parseFunctionBlock(/*allowYield*/ false, /*allowAwait*/ isAsync, /*ignoreMissingOpenBrace*/ true); + // Note: even when 'IgnoreMissingOpenBrace' is passed, parseBody will still error. + return parseFunctionBlock(SignatureFlags.IgnoreMissingOpenBrace | (isAsync ? SignatureFlags.Await : SignatureFlags.None)); } return isAsync @@ -4386,16 +4402,16 @@ namespace ts { parseExpected(SyntaxKind.FunctionKeyword); node.asteriskToken = parseOptionalToken(SyntaxKind.AsteriskToken); - const isGenerator = !!node.asteriskToken; - const isAsync = !!(getModifierFlags(node) & ModifierFlags.Async); + const isGenerator = node.asteriskToken ? SignatureFlags.Yield : SignatureFlags.None; + const isAsync = (getModifierFlags(node) & ModifierFlags.Async) ? SignatureFlags.Await : SignatureFlags.None; node.name = isGenerator && isAsync ? doInYieldAndAwaitContext(parseOptionalIdentifier) : isGenerator ? doInYieldContext(parseOptionalIdentifier) : isAsync ? doInAwaitContext(parseOptionalIdentifier) : parseOptionalIdentifier(); - fillSignature(SyntaxKind.ColonToken, /*yieldContext*/ isGenerator, /*awaitContext*/ isAsync, /*requireCompleteParameterList*/ false, node); - node.body = parseFunctionBlock(/*allowYield*/ isGenerator, /*allowAwait*/ isAsync, /*ignoreMissingOpenBrace*/ false); + fillSignature(SyntaxKind.ColonToken, isGenerator | isAsync, node); + node.body = parseFunctionBlock(isGenerator | isAsync); if (saveDecoratorContext) { setDecoratorContext(/*val*/ true); @@ -4444,12 +4460,12 @@ namespace ts { return finishNode(node); } - function parseFunctionBlock(allowYield: boolean, allowAwait: boolean, ignoreMissingOpenBrace: boolean, diagnosticMessage?: DiagnosticMessage): Block { + function parseFunctionBlock(flags: SignatureFlags, diagnosticMessage?: DiagnosticMessage): Block { const savedYieldContext = inYieldContext(); - setYieldContext(allowYield); + setYieldContext(!!(flags & SignatureFlags.Yield)); const savedAwaitContext = inAwaitContext(); - setAwaitContext(allowAwait); + setAwaitContext(!!(flags & SignatureFlags.Await)); // We may be in a [Decorator] context when parsing a function expression or // arrow function. The body of the function is not in [Decorator] context. @@ -4458,7 +4474,7 @@ namespace ts { setDecoratorContext(/*val*/ false); } - const block = parseBlock(ignoreMissingOpenBrace, diagnosticMessage); + const block = parseBlock(!!(flags & SignatureFlags.IgnoreMissingOpenBrace), diagnosticMessage); if (saveDecoratorContext) { setDecoratorContext(/*val*/ true); @@ -5005,13 +5021,13 @@ namespace ts { return !scanner.hasPrecedingLineBreak() && (isIdentifier() || token() === SyntaxKind.StringLiteral); } - function parseFunctionBlockOrSemicolon(isGenerator: boolean, isAsync: boolean, diagnosticMessage?: DiagnosticMessage): Block { + function parseFunctionBlockOrSemicolon(flags: SignatureFlags, diagnosticMessage?: DiagnosticMessage): Block { if (token() !== SyntaxKind.OpenBraceToken && canParseSemicolon()) { parseSemicolon(); return; } - return parseFunctionBlock(isGenerator, isAsync, /*ignoreMissingOpenBrace*/ false, diagnosticMessage); + return parseFunctionBlock(flags, diagnosticMessage); } // DECLARATIONS @@ -5146,10 +5162,10 @@ namespace ts { parseExpected(SyntaxKind.FunctionKeyword); node.asteriskToken = parseOptionalToken(SyntaxKind.AsteriskToken); node.name = hasModifier(node, ModifierFlags.Default) ? parseOptionalIdentifier() : parseIdentifier(); - const isGenerator = !!node.asteriskToken; - const isAsync = hasModifier(node, ModifierFlags.Async); - fillSignature(SyntaxKind.ColonToken, /*yieldContext*/ isGenerator, /*awaitContext*/ isAsync, /*requireCompleteParameterList*/ false, node); - node.body = parseFunctionBlockOrSemicolon(isGenerator, isAsync, Diagnostics.or_expected); + const isGenerator = node.asteriskToken ? SignatureFlags.Yield : SignatureFlags.None; + const isAsync = hasModifier(node, ModifierFlags.Async) ? SignatureFlags.Await : SignatureFlags.None; + fillSignature(SyntaxKind.ColonToken, isGenerator | isAsync, node); + node.body = parseFunctionBlockOrSemicolon(isGenerator | isAsync, Diagnostics.or_expected); return addJSDocComment(finishNode(node)); } @@ -5158,8 +5174,8 @@ namespace ts { node.decorators = decorators; node.modifiers = modifiers; parseExpected(SyntaxKind.ConstructorKeyword); - fillSignature(SyntaxKind.ColonToken, /*yieldContext*/ false, /*awaitContext*/ false, /*requireCompleteParameterList*/ false, node); - node.body = parseFunctionBlockOrSemicolon(/*isGenerator*/ false, /*isAsync*/ false, Diagnostics.or_expected); + fillSignature(SyntaxKind.ColonToken, SignatureFlags.None, node); + node.body = parseFunctionBlockOrSemicolon(SignatureFlags.None, Diagnostics.or_expected); return addJSDocComment(finishNode(node)); } @@ -5170,10 +5186,10 @@ namespace ts { method.asteriskToken = asteriskToken; method.name = name; method.questionToken = questionToken; - const isGenerator = !!asteriskToken; - const isAsync = hasModifier(method, ModifierFlags.Async); - fillSignature(SyntaxKind.ColonToken, /*yieldContext*/ isGenerator, /*awaitContext*/ isAsync, /*requireCompleteParameterList*/ false, method); - method.body = parseFunctionBlockOrSemicolon(isGenerator, isAsync, diagnosticMessage); + const isGenerator = asteriskToken ? SignatureFlags.Yield : SignatureFlags.None; + const isAsync = hasModifier(method, ModifierFlags.Async) ? SignatureFlags.Await : SignatureFlags.None; + fillSignature(SyntaxKind.ColonToken, isGenerator | isAsync, method); + method.body = parseFunctionBlockOrSemicolon(isGenerator | isAsync, diagnosticMessage); return addJSDocComment(finishNode(method)); } @@ -5226,8 +5242,8 @@ namespace ts { node.decorators = decorators; node.modifiers = modifiers; node.name = parsePropertyName(); - fillSignature(SyntaxKind.ColonToken, /*yieldContext*/ false, /*awaitContext*/ false, /*requireCompleteParameterList*/ false, node); - node.body = parseFunctionBlockOrSemicolon(/*isGenerator*/ false, /*isAsync*/ false); + fillSignature(SyntaxKind.ColonToken, SignatureFlags.None, node); + node.body = parseFunctionBlockOrSemicolon(SignatureFlags.None); return addJSDocComment(finishNode(node)); } diff --git a/src/compiler/program.ts b/src/compiler/program.ts index 97f6f36f3a1..153e4506ea9 100644 --- a/src/compiler/program.ts +++ b/src/compiler/program.ts @@ -447,7 +447,7 @@ namespace ts { let moduleResolutionCache: ModuleResolutionCache; let resolveModuleNamesWorker: (moduleNames: string[], containingFile: string) => ResolvedModuleFull[]; if (host.resolveModuleNames) { - resolveModuleNamesWorker = (moduleNames, containingFile) => host.resolveModuleNames(moduleNames, containingFile).map(resolved => { + resolveModuleNamesWorker = (moduleNames, containingFile) => host.resolveModuleNames(checkAllDefined(moduleNames), containingFile).map(resolved => { // An older host may have omitted extension, in which case we should infer it from the file extension of resolvedFileName. if (!resolved || (resolved as ResolvedModuleFull).extension !== undefined) { return resolved as ResolvedModuleFull; @@ -460,16 +460,16 @@ namespace ts { else { moduleResolutionCache = createModuleResolutionCache(currentDirectory, x => host.getCanonicalFileName(x)); const loader = (moduleName: string, containingFile: string) => resolveModuleName(moduleName, containingFile, options, host, moduleResolutionCache).resolvedModule; - resolveModuleNamesWorker = (moduleNames, containingFile) => loadWithLocalCache(moduleNames, containingFile, loader); + resolveModuleNamesWorker = (moduleNames, containingFile) => loadWithLocalCache(checkAllDefined(moduleNames), containingFile, loader); } let resolveTypeReferenceDirectiveNamesWorker: (typeDirectiveNames: string[], containingFile: string) => ResolvedTypeReferenceDirective[]; if (host.resolveTypeReferenceDirectives) { - resolveTypeReferenceDirectiveNamesWorker = (typeDirectiveNames, containingFile) => host.resolveTypeReferenceDirectives(typeDirectiveNames, containingFile); + resolveTypeReferenceDirectiveNamesWorker = (typeDirectiveNames, containingFile) => host.resolveTypeReferenceDirectives(checkAllDefined(typeDirectiveNames), containingFile); } else { const loader = (typesRef: string, containingFile: string) => resolveTypeReferenceDirective(typesRef, containingFile, options, host).resolvedTypeReferenceDirective; - resolveTypeReferenceDirectiveNamesWorker = (typeReferenceDirectiveNames, containingFile) => loadWithLocalCache(typeReferenceDirectiveNames, containingFile, loader); + resolveTypeReferenceDirectiveNamesWorker = (typeReferenceDirectiveNames, containingFile) => loadWithLocalCache(checkAllDefined(typeReferenceDirectiveNames), containingFile, loader); } const filesByName = createMap(); @@ -2127,4 +2127,9 @@ namespace ts { return options.allowJs ? undefined : Diagnostics.Module_0_was_resolved_to_1_but_allowJs_is_not_set; } } + + function checkAllDefined(names: string[]): string[] { + Debug.assert(names.every(name => name !== undefined), "A name is undefined.", () => JSON.stringify(names)); + return names; + } } diff --git a/src/compiler/scanner.ts b/src/compiler/scanner.ts index dd43b3c8263..16555ff7937 100644 --- a/src/compiler/scanner.ts +++ b/src/compiler/scanner.ts @@ -326,13 +326,20 @@ namespace ts { } export function getPositionOfLineAndCharacter(sourceFile: SourceFile, line: number, character: number): number { - return computePositionOfLineAndCharacter(getLineStarts(sourceFile), line, character); + return computePositionOfLineAndCharacter(getLineStarts(sourceFile), line, character, sourceFile.text); } /* @internal */ - export function computePositionOfLineAndCharacter(lineStarts: number[], line: number, character: number): number { + export function computePositionOfLineAndCharacter(lineStarts: number[], line: number, character: number, debugText?: string): number { Debug.assert(line >= 0 && line < lineStarts.length); - return lineStarts[line] + character; + const res = lineStarts[line] + character; + if (line < lineStarts.length - 1) { + Debug.assert(res < lineStarts[line + 1]); + } + else if (debugText !== undefined) { + Debug.assert(res < debugText.length); + } + return res; } /* @internal */ diff --git a/src/compiler/sys.ts b/src/compiler/sys.ts index d5102da0b3f..3e3ff90c00c 100644 --- a/src/compiler/sys.ts +++ b/src/compiler/sys.ts @@ -39,7 +39,7 @@ namespace ts { getExecutingFilePath(): string; getCurrentDirectory(): string; getDirectories(path: string): string[]; - readDirectory(path: string, extensions?: string[], exclude?: string[], include?: string[], depth?: number): string[]; + readDirectory(path: string, extensions?: ReadonlyArray, exclude?: ReadonlyArray, include?: ReadonlyArray, depth?: number): string[]; getModifiedTime?(path: string): Date; /** * This should be cryptographically secure. @@ -100,7 +100,7 @@ namespace ts { readFile(path: string): string; writeFile(path: string, contents: string): void; getDirectories(path: string): string[]; - readDirectory(path: string, extensions?: string[], basePaths?: string[], excludeEx?: string, includeFileEx?: string, includeDirEx?: string): string[]; + readDirectory(path: string, extensions?: ReadonlyArray, basePaths?: ReadonlyArray, excludeEx?: string, includeFileEx?: string, includeDirEx?: string): string[]; watchFile?(path: string, callback: FileWatcherCallback): FileWatcher; watchDirectory?(path: string, callback: DirectoryWatcherCallback, recursive?: boolean): FileWatcher; realpath(path: string): string; @@ -287,7 +287,7 @@ namespace ts { } } - function readDirectory(path: string, extensions?: string[], excludes?: string[], includes?: string[], depth?: number): string[] { + function readDirectory(path: string, extensions?: ReadonlyArray, excludes?: ReadonlyArray, includes?: ReadonlyArray, depth?: number): string[] { return matchFiles(path, extensions, excludes, includes, useCaseSensitiveFileNames, process.cwd(), depth, getAccessibleFileSystemEntries); } diff --git a/src/compiler/transformers/es2015.ts b/src/compiler/transformers/es2015.ts index 3de73440193..19f9d00c656 100644 --- a/src/compiler/transformers/es2015.ts +++ b/src/compiler/transformers/es2015.ts @@ -3579,7 +3579,7 @@ namespace ts { // Map spans of spread expressions into their expressions and spans of other // expressions into an array literal. const numElements = elements.length; - const segments = flatten( + const segments = flatten( spanMap(elements, partitionSpread, (partition, visitPartition, _start, end) => visitPartition(partition, multiLine, hasTrailingComma && end === numElements) ) diff --git a/src/compiler/transformers/jsx.ts b/src/compiler/transformers/jsx.ts index eab5f69bd4f..ca22cb701d0 100644 --- a/src/compiler/transformers/jsx.ts +++ b/src/compiler/transformers/jsx.ts @@ -88,7 +88,7 @@ namespace ts { else { // Map spans of JsxAttribute nodes into object literals and spans // of JsxSpreadAttribute nodes into expressions. - const segments = flatten( + const segments = flatten( spanMap(attrs, isJsxSpreadAttribute, (attrs, isSpread) => isSpread ? map(attrs, transformJsxSpreadAttributeToExpression) : createObjectLiteral(map(attrs, transformJsxAttributeToObjectLiteralElement)) diff --git a/src/compiler/transformers/ts.ts b/src/compiler/transformers/ts.ts index 5d6efa489b5..5fd1b7b62da 100644 --- a/src/compiler/transformers/ts.ts +++ b/src/compiler/transformers/ts.ts @@ -1377,7 +1377,7 @@ namespace ts { const decoratorExpressions: Expression[] = []; addRange(decoratorExpressions, map(allDecorators.decorators, transformDecorator)); - addRange(decoratorExpressions, flatMap(allDecorators.parameters, transformDecoratorsOfParameter)); + addRange(decoratorExpressions, flatMap(allDecorators.parameters, transformDecoratorsOfParameter)); addTypeMetadata(node, container, decoratorExpressions); return decoratorExpressions; } diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 88bfff872d5..c26fa5603ea 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -2406,7 +2406,7 @@ namespace ts { export interface ParseConfigHost { useCaseSensitiveFileNames: boolean; - readDirectory(rootDir: string, extensions: string[], excludes: string[], includes: string[], depth: number): string[]; + readDirectory(rootDir: string, extensions: ReadonlyArray, excludes: ReadonlyArray, includes: ReadonlyArray, depth: number): string[]; /** * Gets a value indicating whether the specified path exists and is a file. @@ -2890,13 +2890,11 @@ namespace ts { TypeParameter = 1 << 18, // Type parameter TypeAlias = 1 << 19, // Type alias ExportValue = 1 << 20, // Exported value marker (see comment in declareModuleMember in binder) - ExportType = 1 << 21, // Exported type marker (see comment in declareModuleMember in binder) - ExportNamespace = 1 << 22, // Exported namespace marker (see comment in declareModuleMember in binder) - Alias = 1 << 23, // An alias for another symbol (see comment in isAliasSymbolDeclaration in checker) - Prototype = 1 << 24, // Prototype property (no source representation) - ExportStar = 1 << 25, // Export * declaration - Optional = 1 << 26, // Optional property - Transient = 1 << 27, // Transient symbol (created during type check) + Alias = 1 << 21, // An alias for another symbol (see comment in isAliasSymbolDeclaration in checker) + Prototype = 1 << 22, // Prototype property (no source representation) + ExportStar = 1 << 23, // Export * declaration + Optional = 1 << 24, // Optional property + Transient = 1 << 25, // Transient symbol (created during type check) Enum = RegularEnum | ConstEnum, Variable = FunctionScopedVariable | BlockScopedVariable, @@ -2941,7 +2939,6 @@ namespace ts { BlockScoped = BlockScopedVariable | Class | Enum, PropertyOrAccessor = Property | Accessor, - Export = ExportNamespace | ExportType | ExportValue, ClassMember = Method | Accessor | Property, @@ -4354,7 +4351,7 @@ namespace ts { */ export type Visitor = (node: Node) => VisitResult; - export type VisitResult = T | T[]; + export type VisitResult = T | T[] | undefined; export interface Printer { /** diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index 04479e6f9cd..c8751f497a6 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -44,42 +44,47 @@ namespace ts { string(): string; } - // Pool writers to avoid needing to allocate them for every symbol we write. - const stringWriters: StringSymbolWriter[] = []; - export function getSingleLineStringWriter(): StringSymbolWriter { - if (stringWriters.length === 0) { - let str = ""; + const stringWriter = createSingleLineStringWriter(); + let stringWriterAcquired = false; - const writeText: (text: string) => void = text => str += text; - return { - string: () => str, - writeKeyword: writeText, - writeOperator: writeText, - writePunctuation: writeText, - writeSpace: writeText, - writeStringLiteral: writeText, - writeParameter: writeText, - writeProperty: writeText, - writeSymbol: writeText, + function createSingleLineStringWriter(): StringSymbolWriter { + let str = ""; - // Completely ignore indentation for string writers. And map newlines to - // a single space. - writeLine: () => str += " ", - increaseIndent: noop, - decreaseIndent: noop, - clear: () => str = "", - trackSymbol: noop, - reportInaccessibleThisError: noop, - reportPrivateInBaseOfClassExpression: noop, - }; - } + const writeText: (text: string) => void = text => str += text; + return { + string: () => str, + writeKeyword: writeText, + writeOperator: writeText, + writePunctuation: writeText, + writeSpace: writeText, + writeStringLiteral: writeText, + writeParameter: writeText, + writeProperty: writeText, + writeSymbol: writeText, - return stringWriters.pop(); + // Completely ignore indentation for string writers. And map newlines to + // a single space. + writeLine: () => str += " ", + increaseIndent: noop, + decreaseIndent: noop, + clear: () => str = "", + trackSymbol: noop, + reportInaccessibleThisError: noop, + reportPrivateInBaseOfClassExpression: noop, + }; } - export function releaseStringWriter(writer: StringSymbolWriter) { - writer.clear(); - stringWriters.push(writer); + export function usingSingleLineStringWriter(action: (writer: StringSymbolWriter) => void): string { + try { + Debug.assert(!stringWriterAcquired); + stringWriterAcquired = true; + action(stringWriter); + return stringWriter.string(); + } + finally { + stringWriter.clear(); + stringWriterAcquired = false; + } } export function getFullWidth(node: Node) { @@ -1619,7 +1624,7 @@ namespace ts { } export function isRestParameter(node: ParameterDeclaration) { - if (node && (node.flags & NodeFlags.JavaScriptFile)) { + if (isInJavaScriptFile(node)) { if (node.type && node.type.kind === SyntaxKind.JSDocVariadicType || forEach(getJSDocParameterTags(node), t => t.typeExpression && t.typeExpression.type.kind === SyntaxKind.JSDocVariadicType)) { @@ -2743,7 +2748,7 @@ namespace ts { if (node.typeParameters) { return node.typeParameters; } - if (node.flags & NodeFlags.JavaScriptFile) { + if (isInJavaScriptFile(node)) { const templateTag = getJSDocTemplateTag(node); return templateTag && templateTag.typeParameters; } @@ -3334,7 +3339,7 @@ namespace ts { } } - return stableSort(result, (x, y) => compareValues(x[0], y[0])); + return stableSort<[number, string]>(result, (x, y) => compareValues(x[0], y[0])); } export function formatSyntaxKind(kind: SyntaxKind): string { @@ -3598,6 +3603,15 @@ namespace ts { } return previous[previous.length - 1]; } + + export function skipAlias(symbol: Symbol, checker: TypeChecker) { + return symbol.flags & SymbolFlags.Alias ? checker.getAliasedSymbol(symbol) : symbol; + } + + /** See comment on `declareModuleMember` in `binder.ts`. */ + export function getCombinedLocalAndExportSymbolFlags(symbol: Symbol): SymbolFlags { + return symbol.exportSymbol ? symbol.exportSymbol.flags | symbol.flags : symbol.flags; + } } namespace ts { @@ -5116,6 +5130,7 @@ namespace ts { || kind === SyntaxKind.ThisKeyword || kind === SyntaxKind.TrueKeyword || kind === SyntaxKind.SuperKeyword + || kind === SyntaxKind.ImportKeyword || kind === SyntaxKind.NonNullExpression || kind === SyntaxKind.MetaProperty; } diff --git a/src/harness/fourslash.ts b/src/harness/fourslash.ts index 434a3981461..5e83c5339c6 100644 --- a/src/harness/fourslash.ts +++ b/src/harness/fourslash.ts @@ -219,7 +219,7 @@ namespace FourSlash { // Add input file which has matched file name with the given reference-file path. // This is necessary when resolveReference flag is specified - private addMatchedInputFile(referenceFilePath: string, extensions: string[]) { + private addMatchedInputFile(referenceFilePath: string, extensions: ReadonlyArray) { const inputFiles = this.inputFiles; const languageServiceAdapterHost = this.languageServiceAdapterHost; if (!extensions) { @@ -605,7 +605,7 @@ namespace FourSlash { this.verifyGoToXPlain(arg0, endMarkerNames, getDefs); } else if (ts.isArray(arg0)) { - const pairs: [string | string[], string | string[]][] = arg0; + const pairs: ReadonlyArray<[string | string[], string | string[]]> = arg0; for (const [start, end] of pairs) { this.verifyGoToXPlain(start, end, getDefs); } @@ -949,6 +949,22 @@ namespace FourSlash { this.verifySymbol(symbol, declarationRanges); } + public symbolsInScope(range: Range): ts.Symbol[] { + const node = this.goToAndGetNode(range); + return this.getChecker().getSymbolsInScope(node, ts.SymbolFlags.Value | ts.SymbolFlags.Type | ts.SymbolFlags.Namespace); + } + + public verifyTypeOfSymbolAtLocation(range: Range, symbol: ts.Symbol, expected: string): void { + const node = this.goToAndGetNode(range); + const checker = this.getChecker(); + const type = checker.getTypeOfSymbolAtLocation(symbol, node); + + const actual = checker.typeToString(type); + if (actual !== expected) { + this.raiseError(`Expected: '${expected}', actual: '${actual}'`); + } + } + private verifyReferencesAre(expectedReferences: Range[]) { const actualReferences = this.getReferencesAtCaret() || []; @@ -1790,7 +1806,14 @@ namespace FourSlash { this.editScriptAndUpdateMarkers(fileName, offsetStart, offsetEnd, edit.newText); const editDelta = edit.newText.length - edit.span.length; if (offsetStart <= this.currentCaretPosition) { - this.currentCaretPosition += editDelta; + if (offsetEnd <= this.currentCaretPosition) { + // The entirety of the edit span falls before the caret position, shift the caret accordingly + this.currentCaretPosition += editDelta; + } + else { + // The span being replaced includes the caret position, place the caret at the beginning of the span + this.currentCaretPosition = offsetStart; + } } runningOffset += editDelta; // TODO: Consider doing this at least some of the time for higher fidelity. Currently causes a failure (bug 707150) @@ -2320,35 +2343,25 @@ namespace FourSlash { * @param fileName Path to file where error should be retrieved from. */ private getCodeFixActions(fileName: string, errorCode?: number): ts.CodeAction[] { - const diagnosticsForCodeFix = this.getDiagnostics(fileName).map(diagnostic => { - return { - start: diagnostic.start, - length: diagnostic.length, - code: diagnostic.code - }; - }); - const dedupedDiagnositcs = ts.deduplicate(diagnosticsForCodeFix, ts.equalOwnProperties); - - let actions: ts.CodeAction[] = undefined; - - for (const diagnostic of dedupedDiagnositcs) { + const diagnosticsForCodeFix = this.getDiagnostics(fileName).map(diagnostic => ({ + start: diagnostic.start, + length: diagnostic.length, + code: diagnostic.code + })); + return ts.flatMap(ts.deduplicate(diagnosticsForCodeFix, ts.equalOwnProperties), diagnostic => { if (errorCode && errorCode !== diagnostic.code) { - continue; + return; } - const newActions = this.languageService.getCodeFixesAtPosition(fileName, diagnostic.start, diagnostic.length, [diagnostic.code], this.formatCodeSettings); - if (newActions && newActions.length) { - actions = actions ? actions.concat(newActions) : newActions; - } - } - return actions; + return this.languageService.getCodeFixesAtPosition(fileName, diagnostic.start, diagnostic.start + diagnostic.length, [diagnostic.code], this.formatCodeSettings); + }); } private applyCodeActions(actions: ts.CodeAction[], index?: number): void { if (index === undefined) { if (!(actions && actions.length === 1)) { - this.raiseError(`Should find exactly one codefix, but ${actions ? actions.length : "none"} found.`); + this.raiseError(`Should find exactly one codefix, but ${actions ? actions.length : "none"} found. ${actions ? actions.map(a => `${Harness.IO.newLine()} "${a.description}"`) : "" }`); } index = 0; } @@ -2373,7 +2386,7 @@ namespace FourSlash { const codeFixes = this.getCodeFixActions(this.activeFile.fileName, errorCode); - if (!codeFixes || codeFixes.length === 0) { + if (codeFixes.length === 0) { if (expectedTextArray.length !== 0) { this.raiseError("No codefixes returned."); } @@ -2702,11 +2715,11 @@ namespace FourSlash { public verifyCodeFixAvailable(negative: boolean) { const codeFix = this.getCodeFixActions(this.activeFile.fileName); - if (negative && codeFix) { + if (negative && codeFix.length) { this.raiseError(`verifyCodeFixAvailable failed - expected no fixes but found one.`); } - if (!(negative || codeFix)) { + if (!(negative || codeFix.length)) { this.raiseError(`verifyCodeFixAvailable failed - expected code fixes but none found.`); } } @@ -3426,6 +3439,10 @@ namespace FourSlashInterface { public markerByName(s: string): FourSlash.Marker { return this.state.getMarkerByName(s); } + + public symbolsInScope(range: FourSlash.Range): ts.Symbol[] { + return this.state.symbolsInScope(range); + } } export class GoTo { @@ -3694,6 +3711,10 @@ namespace FourSlashInterface { this.state.verifySymbolAtLocation(startRange, declarationRanges); } + public typeOfSymbolAtLocation(range: FourSlash.Range, symbol: ts.Symbol, expected: string) { + this.state.verifyTypeOfSymbolAtLocation(range, symbol, expected); + } + public referencesOf(start: FourSlash.Range, references: FourSlash.Range[]) { this.state.verifyReferencesOf(start, references); } diff --git a/src/harness/harness.ts b/src/harness/harness.ts index 026ff2de85f..d5218695f09 100644 --- a/src/harness/harness.ts +++ b/src/harness/harness.ts @@ -493,7 +493,7 @@ namespace Harness { args(): string[]; getExecutingFilePath(): string; exit(exitCode?: number): void; - readDirectory(path: string, extension?: string[], exclude?: string[], include?: string[], depth?: number): string[]; + readDirectory(path: string, extension?: ReadonlyArray, exclude?: ReadonlyArray, include?: ReadonlyArray, depth?: number): string[]; tryEnableSourceMapsForHost?(): void; getEnvironmentVariable?(name: string): string; } diff --git a/src/harness/harnessLanguageService.ts b/src/harness/harnessLanguageService.ts index 82f0b87be94..339138ba30f 100644 --- a/src/harness/harnessLanguageService.ts +++ b/src/harness/harnessLanguageService.ts @@ -208,7 +208,7 @@ namespace Harness.LanguageService { const script = this.getScriptSnapshot(fileName); return script !== undefined; } - readDirectory(path: string, extensions?: string[], exclude?: string[], include?: string[], depth?: number): string[] { + readDirectory(path: string, extensions?: ReadonlyArray, exclude?: ReadonlyArray, include?: ReadonlyArray, depth?: number): string[] { return ts.matchFiles(path, extensions, exclude, include, /*useCaseSensitiveFileNames*/ false, this.getCurrentDirectory(), diff --git a/src/harness/loggedIO.ts b/src/harness/loggedIO.ts index d4a1e2e2bd0..f113b9ddb9d 100644 --- a/src/harness/loggedIO.ts +++ b/src/harness/loggedIO.ts @@ -64,11 +64,11 @@ interface IOLog { }[]; directoriesRead: { path: string, - extensions: string[], - exclude: string[], - include: string[], + extensions: ReadonlyArray, + exclude: ReadonlyArray, + include: ReadonlyArray, depth: number, - result: string[] + result: ReadonlyArray, }[]; } diff --git a/src/harness/unittests/tsserverProjectSystem.ts b/src/harness/unittests/tsserverProjectSystem.ts index 83e92c2018d..273f7dd1534 100644 --- a/src/harness/unittests/tsserverProjectSystem.ts +++ b/src/harness/unittests/tsserverProjectSystem.ts @@ -585,27 +585,25 @@ namespace ts.projectSystem { return []; } - readDirectory(path: string, extensions?: string[], exclude?: string[], include?: string[], depth?: number): string[] { + readDirectory(path: string, extensions?: ReadonlyArray, exclude?: ReadonlyArray, include?: ReadonlyArray, depth?: number): string[] { return ts.matchFiles(this.toFullPath(path), extensions, exclude, include, this.useCaseSensitiveFileNames, this.getCurrentDirectory(), depth, (dir) => { - const result: FileSystemEntries = { - directories: [], - files: [] - }; + const directories: string[] = []; + const files: string[] = []; const dirEntry = this.fs.get(this.toPath(dir)); if (isFolder(dirEntry)) { dirEntry.entries.forEach((entry) => { if (isFolder(entry)) { - result.directories.push(getBaseFileName(entry.fullPath)); + directories.push(getBaseFileName(entry.fullPath)); } else if (isFile(entry)) { - result.files.push(getBaseFileName(entry.fullPath)); + files.push(getBaseFileName(entry.fullPath)); } else { Debug.fail("Unknown entry"); } }); } - return result; + return { directories, files }; }); } diff --git a/src/harness/unittests/typingsInstaller.ts b/src/harness/unittests/typingsInstaller.ts index b29a619b1b4..287b0fe483d 100644 --- a/src/harness/unittests/typingsInstaller.ts +++ b/src/harness/unittests/typingsInstaller.ts @@ -44,6 +44,18 @@ namespace ts.projectSystem { }); } + function trackingLogger(): { log(message: string): void, finish(): string[] } { + const logs: string[] = []; + return { + log(message) { + logs.push(message); + }, + finish() { + return logs; + } + }; + } + import typingsName = TI.typingsName; describe("local module", () => { @@ -1038,7 +1050,12 @@ namespace ts.projectSystem { const cache = createMap(); const host = createServerHost([app, jquery, chroma]); - const result = JsTyping.discoverTypings(host, [app.path, jquery.path, chroma.path], getDirectoryPath(app.path), /*safeListPath*/ undefined, cache, { enable: true }, []); + const logger = trackingLogger(); + const result = JsTyping.discoverTypings(host, logger.log, [app.path, jquery.path, chroma.path], getDirectoryPath(app.path), /*safeListPath*/ undefined, cache, { enable: true }, []); + assert.deepEqual(logger.finish(), [ + 'Inferred typings from file names: ["jquery","chroma-js"]', + 'Result: {"cachedTypingPaths":[],"newTypingNames":["jquery","chroma-js"],"filesToWatch":["/a/b/bower_components","/a/b/node_modules"]}', + ]); assert.deepEqual(result.newTypingNames, ["jquery", "chroma-js"]); }); @@ -1051,7 +1068,12 @@ namespace ts.projectSystem { const cache = createMap(); for (const name of JsTyping.nodeCoreModuleList) { - const result = JsTyping.discoverTypings(host, [f.path], getDirectoryPath(f.path), /*safeListPath*/ undefined, cache, { enable: true }, [name, "somename"]); + const logger = trackingLogger(); + const result = JsTyping.discoverTypings(host, logger.log, [f.path], getDirectoryPath(f.path), /*safeListPath*/ undefined, cache, { enable: true }, [name, "somename"]); + assert.deepEqual(logger.finish(), [ + 'Inferred typings from unresolved imports: ["node","somename"]', + 'Result: {"cachedTypingPaths":[],"newTypingNames":["node","somename"],"filesToWatch":["/a/b/bower_components","/a/b/node_modules"]}', + ]); assert.deepEqual(result.newTypingNames.sort(), ["node", "somename"]); } }); @@ -1067,7 +1089,12 @@ namespace ts.projectSystem { }; const host = createServerHost([f, node]); const cache = createMapFromTemplate({ "node": node.path }); - const result = JsTyping.discoverTypings(host, [f.path], getDirectoryPath(f.path), /*safeListPath*/ undefined, cache, { enable: true }, ["fs", "bar"]); + const logger = trackingLogger(); + const result = JsTyping.discoverTypings(host, logger.log, [f.path], getDirectoryPath(f.path), /*safeListPath*/ undefined, cache, { enable: true }, ["fs", "bar"]); + assert.deepEqual(logger.finish(), [ + 'Inferred typings from unresolved imports: ["node","bar"]', + 'Result: {"cachedTypingPaths":["/a/b/node.d.ts"],"newTypingNames":["bar"],"filesToWatch":["/a/b/bower_components","/a/b/node_modules"]}', + ]); assert.deepEqual(result.cachedTypingPaths, [node.path]); assert.deepEqual(result.newTypingNames, ["bar"]); }); @@ -1087,7 +1114,12 @@ namespace ts.projectSystem { }; const host = createServerHost([app, a, b]); const cache = createMap(); - const result = JsTyping.discoverTypings(host, [app.path], getDirectoryPath(app.path), /*safeListPath*/ undefined, cache, { enable: true }, /*unresolvedImports*/ []); + const logger = trackingLogger(); + const result = JsTyping.discoverTypings(host, logger.log, [app.path], getDirectoryPath(app.path), /*safeListPath*/ undefined, cache, { enable: true }, /*unresolvedImports*/ []); + assert.deepEqual(logger.finish(), [ + 'Searching for typing names in /node_modules; all files: ["/node_modules/a/package.json"]', + 'Result: {"cachedTypingPaths":[],"newTypingNames":["a"],"filesToWatch":["/bower_components","/node_modules"]}', + ]); assert.deepEqual(result, { cachedTypingPaths: [], newTypingNames: ["a"], // But not "b" diff --git a/src/harness/unittests/versionCache.ts b/src/harness/unittests/versionCache.ts index d6e0a7c2278..10790d41302 100644 --- a/src/harness/unittests/versionCache.ts +++ b/src/harness/unittests/versionCache.ts @@ -7,8 +7,7 @@ namespace ts { } function lineColToPosition(lineIndex: server.LineIndex, line: number, col: number) { - const lineInfo = lineIndex.lineNumberToInfo(line); - return (lineInfo.offset + col - 1); + return lineIndex.absolutePositionOfStartOfLine(line) + (col - 1); } function validateEdit(lineIndex: server.LineIndex, sourceText: string, position: number, deleteLength: number, insertString: string): void { @@ -298,20 +297,17 @@ and grew 1cm per day`; it("Line/offset from pos", () => { for (let i = 0; i < iterationCount; i++) { - const lp = lineIndex.charOffsetToLineNumberAndPos(rsa[i]); + const lp = lineIndex.positionToLineOffset(rsa[i]); const lac = ts.computeLineAndCharacterOfPosition(lineMap, rsa[i]); assert.equal(lac.line + 1, lp.line, "Line number mismatch " + (lac.line + 1) + " " + lp.line + " " + i); - assert.equal(lac.character, (lp.offset), "Charachter offset mismatch " + lac.character + " " + lp.offset + " " + i); + assert.equal(lac.character, lp.offset - 1, "Character offset mismatch " + lac.character + " " + (lp.offset - 1) + " " + i); } }); it("Start pos from line", () => { for (let i = 0; i < iterationCount; i++) { for (let j = 0; j < lines.length; j++) { - const lineInfo = lineIndex.lineNumberToInfo(j + 1); - const lineIndexOffset = lineInfo.offset; - const lineMapOffset = lineMap[j]; - assert.equal(lineIndexOffset, lineMapOffset); + assert.equal(lineIndex.absolutePositionOfStartOfLine(j + 1), lineMap[j]); } } }); diff --git a/src/harness/virtualFileSystem.ts b/src/harness/virtualFileSystem.ts index 3904a27bb1c..da7784770df 100644 --- a/src/harness/virtualFileSystem.ts +++ b/src/harness/virtualFileSystem.ts @@ -216,7 +216,7 @@ namespace Utils { } } - readDirectory(path: string, extensions: string[], excludes: string[], includes: string[], depth: number) { + readDirectory(path: string, extensions: ReadonlyArray, excludes: ReadonlyArray, includes: ReadonlyArray, depth: number) { return ts.matchFiles(path, extensions, excludes, includes, this.useCaseSensitiveFileNames, this.currentDirectory, depth, (path: string) => this.getAccessibleFileSystemEntries(path)); } } diff --git a/src/server/builder.ts b/src/server/builder.ts index 7d35247e974..8fd8fbf1e65 100644 --- a/src/server/builder.ts +++ b/src/server/builder.ts @@ -277,7 +277,7 @@ namespace ts.server { const referencedFilePaths = this.project.getReferencedFiles(fileInfo.scriptInfo.path); if (referencedFilePaths.length > 0) { - return map(referencedFilePaths, f => this.getOrCreateFileInfo(f)).sort(ModuleBuilderFileInfo.compareFileInfos); + return map(referencedFilePaths, f => this.getOrCreateFileInfo(f)).sort(ModuleBuilderFileInfo.compareFileInfos); } return []; } diff --git a/src/server/client.ts b/src/server/client.ts index f0be887814d..a7e0615dce8 100644 --- a/src/server/client.ts +++ b/src/server/client.ts @@ -227,7 +227,7 @@ namespace ts.server { })); } - getFormattingEditsForRange(file: string, start: number, end: number, _options: ts.FormatCodeOptions): ts.TextChange[] { + getFormattingEditsForRange(file: string, start: number, end: number, _options: FormatCodeOptions): ts.TextChange[] { const args: protocol.FormatRequestArgs = this.createFileLocationRequestArgsWithEndLineAndOffset(file, start, end); diff --git a/src/server/editorServices.ts b/src/server/editorServices.ts index 6d9be7a6520..1b2793e5f2f 100644 --- a/src/server/editorServices.ts +++ b/src/server/editorServices.ts @@ -45,7 +45,7 @@ namespace ts.server { * Any compiler options that might contain paths will be taken out. * Enum compiler options will be converted to strings. */ - readonly compilerOptions: ts.CompilerOptions; + readonly compilerOptions: CompilerOptions; // "extends", "files", "include", or "exclude" will be undefined if an external config is used. // Otherwise, we will use "true" if the property is present and "false" if it is missing. readonly extends: boolean | undefined; @@ -201,7 +201,7 @@ namespace ts.server { * This helper function processes a list of projects and return the concatenated, sortd and deduplicated output of processing each project. */ export function combineProjectOutput(projects: Project[], action: (project: Project) => T[], comparer?: (a: T, b: T) => number, areEqual?: (a: T, b: T) => boolean) { - const result = projects.reduce((previous, current) => concatenate(previous, action(current)), []).sort(comparer); + const result = flatMap(projects, action).sort(comparer); return projects.length > 1 ? deduplicate(result, areEqual) : result; } @@ -226,7 +226,7 @@ namespace ts.server { getFileName: x => x, getScriptKind: _ => undefined, hasMixedContent: (fileName, extraFileExtensions) => { - const mixedContentExtensions = ts.map(ts.filter(extraFileExtensions, item => item.isMixedContent), item => item.extension); + const mixedContentExtensions = map(filter(extraFileExtensions, item => item.isMixedContent), item => item.extension); return forEach(mixedContentExtensions, extension => fileExtensionIs(fileName, extension)); } }; @@ -663,7 +663,7 @@ namespace ts.server { // If a change was made inside "folder/file", node will trigger the callback twice: // one with the fileName being "folder/file", and the other one with "folder". // We don't respond to the second one. - if (fileName && !ts.isSupportedSourceFileName(fileName, project.getCompilerOptions(), this.hostConfiguration.extraFileExtensions)) { + if (fileName && !isSupportedSourceFileName(fileName, project.getCompilerOptions(), this.hostConfiguration.extraFileExtensions)) { return; } @@ -1120,7 +1120,7 @@ namespace ts.server { configFileName: configFileName(), projectType: project instanceof server.ExternalProject ? "external" : "configured", languageServiceEnabled: project.languageServiceEnabled, - version: ts.version, + version, }; this.eventHandler({ eventName: ProjectInfoTelemetryEvent, data }); @@ -1130,7 +1130,7 @@ namespace ts.server { } const configFilePath = project instanceof server.ConfiguredProject && project.getConfigFilePath(); - const base = ts.getBaseFileName(configFilePath); + const base = getBaseFileName(configFilePath); return base === "tsconfig.json" || base === "jsconfig.json" ? base : "other"; } diff --git a/src/server/lsHost.ts b/src/server/lsHost.ts index 133db8cac2c..b54f493be1b 100644 --- a/src/server/lsHost.ts +++ b/src/server/lsHost.ts @@ -3,8 +3,6 @@ /// namespace ts.server { - type NameResolutionWithFailedLookupLocations = { failedLookupLocations: string[], isInvalidated?: boolean }; - export class CachedServerHost implements ServerHost { args: string[]; newLine: string; @@ -32,7 +30,7 @@ namespace ts.server { } private getFileSystemEntries(rootDir: string) { - const path = ts.toPath(rootDir, this.currentDirectory, this.getCanonicalFileName); + const path = toPath(rootDir, this.currentDirectory, this.getCanonicalFileName); const cachedResult = this.cachedReadDirectoryResult.get(path); if (cachedResult) { return cachedResult; @@ -49,7 +47,7 @@ namespace ts.server { private canWorkWithCacheForDir(rootDir: string) { // Some of the hosts might not be able to handle read directory or getDirectories - const path = ts.toPath(rootDir, this.currentDirectory, this.getCanonicalFileName); + const path = toPath(rootDir, this.currentDirectory, this.getCanonicalFileName); if (this.cachedReadDirectoryResult.get(path)) { return true; } @@ -66,11 +64,11 @@ namespace ts.server { } writeFile(fileName: string, data: string, writeByteOrderMark?: boolean) { - const path = ts.toPath(fileName, this.currentDirectory, this.getCanonicalFileName); + const path = toPath(fileName, this.currentDirectory, this.getCanonicalFileName); const result = this.cachedReadDirectoryResult.get(getDirectoryPath(path)); const baseFileName = getBaseFileName(toNormalizedPath(fileName)); - if (result && !some(result.files, file => this.fileNameEqual(file, baseFileName))) { - result.files.push(baseFileName); + if (result) { + result.files = this.updateFileSystemEntry(result.files, baseFileName, /*isValid*/ true); } return this.host.writeFile(fileName, data, writeByteOrderMark); } @@ -102,7 +100,7 @@ namespace ts.server { getDirectories(rootDir: string) { if (this.canWorkWithCacheForDir(rootDir)) { - return this.getFileSystemEntries(rootDir).directories; + return this.getFileSystemEntries(rootDir).directories.slice(); } return this.host.getDirectories(rootDir); } @@ -115,14 +113,14 @@ namespace ts.server { } fileExists(fileName: string): boolean { - const path = ts.toPath(fileName, this.currentDirectory, this.getCanonicalFileName); + const path = toPath(fileName, this.currentDirectory, this.getCanonicalFileName); const result = this.cachedReadDirectoryResult.get(getDirectoryPath(path)); const baseName = getBaseFileName(toNormalizedPath(fileName)); - return (result && some(result.files, file => this.fileNameEqual(file, baseName))) || this.host.fileExists(fileName); + return (result && this.hasEntry(result.files, baseName)) || this.host.fileExists(fileName); } directoryExists(dirPath: string) { - const path = ts.toPath(dirPath, this.currentDirectory, this.getCanonicalFileName); + const path = toPath(dirPath, this.currentDirectory, this.getCanonicalFileName); return this.cachedReadDirectoryResult.has(path) || this.host.directoryExists(dirPath); } @@ -134,15 +132,20 @@ namespace ts.server { return this.getCanonicalFileName(name1) === this.getCanonicalFileName(name2); } - private updateFileSystemEntry(entries: string[], baseName: string, isValid: boolean) { - if (some(entries, entry => this.fileNameEqual(entry, baseName))) { + private hasEntry(entries: ReadonlyArray, name: string) { + return some(entries, file => this.fileNameEqual(file, name)); + } + + private updateFileSystemEntry(entries: ReadonlyArray, baseName: string, isValid: boolean) { + if (this.hasEntry(entries, baseName)) { if (!isValid) { - filterMutate(entries, entry => !this.fileNameEqual(entry, baseName)); + return filter(entries, entry => !this.fileNameEqual(entry, baseName)); } } else if (isValid) { - entries.push(baseName); + return entries.concat(baseName); } + return entries; } addOrDeleteFileOrFolder(fileOrFolder: NormalizedPath) { @@ -159,8 +162,8 @@ namespace ts.server { if (parentResult) { const baseName = getBaseFileName(fileOrFolder); if (parentResult) { - this.updateFileSystemEntry(parentResult.files, baseName, this.host.fileExists(path)); - this.updateFileSystemEntry(parentResult.directories, baseName, this.host.directoryExists(path)); + parentResult.files = this.updateFileSystemEntry(parentResult.files, baseName, this.host.fileExists(path)); + parentResult.directories = this.updateFileSystemEntry(parentResult.directories, baseName, this.host.directoryExists(path)); } } } @@ -185,8 +188,9 @@ namespace ts.server { } - export class LSHost implements ts.LanguageServiceHost, ModuleResolutionHost { - private compilationSettings: ts.CompilerOptions; + type NameResolutionWithFailedLookupLocations = { failedLookupLocations: string[], isInvalidated?: boolean }; + export class LSHost implements LanguageServiceHost, ModuleResolutionHost { + private compilationSettings: CompilerOptions; private readonly resolvedModuleNames = createMap>(); private readonly resolvedTypeReferenceDirectives = createMap>(); /* @internal */ @@ -203,7 +207,7 @@ namespace ts.server { constructor(host: ServerHost, private project: Project, private readonly cancellationToken: HostCancellationToken) { this.host = host; this.cancellationToken = new ThrottledCancellationToken(cancellationToken, project.projectService.throttleWaitMilliseconds); - this.getCanonicalFileName = ts.createGetCanonicalFileName(this.host.useCaseSensitiveFileNames); + this.getCanonicalFileName = createGetCanonicalFileName(this.host.useCaseSensitiveFileNames); if (host.trace) { this.trace = s => host.trace(s); @@ -285,7 +289,7 @@ namespace ts.server { } } - ts.Debug.assert(resolution !== undefined); + Debug.assert(resolution !== undefined); resolvedModules.push(getResult(resolution)); } @@ -363,7 +367,7 @@ namespace ts.server { return combinePaths(nodeModuleBinDir, getDefaultLibFileName(this.compilationSettings)); } - getScriptSnapshot(filename: string): ts.IScriptSnapshot { + getScriptSnapshot(filename: string): IScriptSnapshot { const scriptInfo = this.project.getScriptInfoLSHost(filename); if (scriptInfo) { return scriptInfo.getSnapshot(); @@ -411,7 +415,7 @@ namespace ts.server { return this.host.directoryExists(path); } - readDirectory(path: string, extensions?: string[], exclude?: string[], include?: string[], depth?: number): string[] { + readDirectory(path: string, extensions?: ReadonlyArray, exclude?: ReadonlyArray, include?: ReadonlyArray, depth?: number): string[] { return this.host.readDirectory(path, extensions, exclude, include, depth); } @@ -450,7 +454,7 @@ namespace ts.server { }); } - setCompilationSettings(opt: ts.CompilerOptions) { + setCompilationSettings(opt: CompilerOptions) { if (changesAffectModuleResolution(this.compilationSettings, opt)) { this.resolvedModuleNames.clear(); this.resolvedTypeReferenceDirectives.clear(); diff --git a/src/server/project.ts b/src/server/project.ts index 5b78f1f6630..9db74efeb6b 100644 --- a/src/server/project.ts +++ b/src/server/project.ts @@ -115,7 +115,7 @@ namespace ts.server { export abstract class Project { private rootFiles: ScriptInfo[] = []; private rootFilesMap: Map = createMap(); - private program: ts.Program; + private program: Program; private externalFiles: SortedReadonlyArray; private missingFilesMap: Map = createMap(); @@ -191,7 +191,7 @@ namespace ts.server { private readonly projectName: string, readonly projectKind: ProjectKind, readonly projectService: ProjectService, - private documentRegistry: ts.DocumentRegistry, + private documentRegistry: DocumentRegistry, hasExplicitListOfFiles: boolean, languageServiceEnabled: boolean, private compilerOptions: CompilerOptions, @@ -199,7 +199,7 @@ namespace ts.server { host: ServerHost) { if (!this.compilerOptions) { - this.compilerOptions = ts.getDefaultCompilerOptions(); + this.compilerOptions = getDefaultCompilerOptions(); this.compilerOptions.allowNonTsExtensions = true; this.compilerOptions.allowJs = true; } @@ -213,7 +213,7 @@ namespace ts.server { this.lsHost = new LSHost(host, this, this.projectService.cancellationToken); this.lsHost.setCompilationSettings(this.compilerOptions); - this.languageService = ts.createLanguageService(this.lsHost, this.documentRegistry); + this.languageService = createLanguageService(this.lsHost, this.documentRegistry); if (!languageServiceEnabled) { this.disableLanguageService(); @@ -729,7 +729,7 @@ namespace ts.server { } let strBuilder = ""; for (const file of this.program.getSourceFiles()) { - strBuilder += `${file.fileName}\n`; + strBuilder += `\t${file.fileName}\n`; } return strBuilder; } @@ -914,7 +914,7 @@ namespace ts.server { // Used to keep track of what directories are watched for this project directoriesWatchedForTsconfig: string[] = []; - constructor(projectService: ProjectService, documentRegistry: ts.DocumentRegistry, compilerOptions: CompilerOptions) { + constructor(projectService: ProjectService, documentRegistry: DocumentRegistry, compilerOptions: CompilerOptions) { super(InferredProject.newName(), ProjectKind.Inferred, projectService, @@ -995,7 +995,7 @@ namespace ts.server { constructor(configFileName: NormalizedPath, projectService: ProjectService, - documentRegistry: ts.DocumentRegistry, + documentRegistry: DocumentRegistry, hasExplicitListOfFiles: boolean, compilerOptions: CompilerOptions, languageServiceEnabled: boolean, @@ -1232,7 +1232,7 @@ namespace ts.server { } getEffectiveTypeRoots() { - return ts.getEffectiveTypeRoots(this.getCompilerOptions(), this.lsHost.host) || []; + return getEffectiveTypeRoots(this.getCompilerOptions(), this.lsHost.host) || []; } } @@ -1244,7 +1244,7 @@ namespace ts.server { private typeAcquisition: TypeAcquisition; constructor(public externalProjectName: string, projectService: ProjectService, - documentRegistry: ts.DocumentRegistry, + documentRegistry: DocumentRegistry, compilerOptions: CompilerOptions, languageServiceEnabled: boolean, public compileOnSaveEnabled: boolean, diff --git a/src/server/protocol.ts b/src/server/protocol.ts index 916d9c51a14..b6756a7d4ff 100644 --- a/src/server/protocol.ts +++ b/src/server/protocol.ts @@ -640,7 +640,7 @@ namespace ts.server.protocol { } /** - * Location in source code expressed as (one-based) line and character offset. + * Location in source code expressed as (one-based) line and (one-based) column offset. */ export interface Location { line: number; diff --git a/src/server/scriptInfo.ts b/src/server/scriptInfo.ts index 90853124815..3da4cdb14af 100644 --- a/src/server/scriptInfo.ts +++ b/src/server/scriptInfo.ts @@ -61,7 +61,7 @@ namespace ts.server { : ScriptSnapshot.fromString(this.getOrLoadText()); } - public getLineInfo(line: number) { + public getLineInfo(line: number): AbsolutePositionAndLineText { return this.switchToScriptVersionCache().getSnapshot().index.lineNumberToInfo(line); } /** @@ -72,19 +72,12 @@ namespace ts.server { const lineMap = this.getLineMap(); const start = lineMap[line]; // -1 since line is 1-based const end = line + 1 < lineMap.length ? lineMap[line + 1] : this.text.length; - return ts.createTextSpanFromBounds(start, end); + return createTextSpanFromBounds(start, end); } const index = this.svc.getSnapshot().index; - const lineInfo = index.lineNumberToInfo(line + 1); - let len: number; - if (lineInfo.leaf) { - len = lineInfo.leaf.text.length; - } - else { - const nextLineInfo = index.lineNumberToInfo(line + 2); - len = nextLineInfo.offset - lineInfo.offset; - } - return ts.createTextSpan(lineInfo.offset, len); + const { lineText, absolutePosition } = index.lineNumberToInfo(line + 1); + const len = lineText !== undefined ? lineText.length : index.absolutePositionOfStartOfLine(line + 2) - absolutePosition; + return createTextSpan(absolutePosition, len); } /** @@ -93,27 +86,19 @@ namespace ts.server { */ lineOffsetToPosition(line: number, offset: number): number { if (!this.svc) { - return computePositionOfLineAndCharacter(this.getLineMap(), line - 1, offset - 1); + return computePositionOfLineAndCharacter(this.getLineMap(), line - 1, offset - 1, this.text); } - const index = this.svc.getSnapshot().index; - const lineInfo = index.lineNumberToInfo(line); // TODO: assert this offset is actually on the line - return (lineInfo.offset + offset - 1); + return this.svc.getSnapshot().index.absolutePositionOfStartOfLine(line) + (offset - 1); } - /** - * @param line 1-based index - * @param offset 1-based index - */ - positionToLineOffset(position: number): ILineInfo { + positionToLineOffset(position: number): protocol.Location { if (!this.svc) { const { line, character } = computeLineAndCharacterOfPosition(this.getLineMap(), position); return { line: line + 1, offset: character + 1 }; } - const index = this.svc.getSnapshot().index; - const lineOffset = index.charOffsetToLineNumberAndPos(position); - return { line: lineOffset.line, offset: lineOffset.offset + 1 }; + return this.svc.getSnapshot().index.positionToLineOffset(position); } private getFileText(tempFileName?: string) { @@ -162,7 +147,7 @@ namespace ts.server { * All projects that include this file */ readonly containingProjects: Project[] = []; - private formatCodeSettings: ts.FormatCodeSettings; + private formatCodeSettings: FormatCodeSettings; readonly path: Path; private fileWatcher: FileWatcher; @@ -343,7 +328,7 @@ namespace ts.server { } } - getLineInfo(line: number) { + getLineInfo(line: number): AbsolutePositionAndLineText { return this.textStorage.getLineInfo(line); } @@ -373,11 +358,7 @@ namespace ts.server { return this.textStorage.lineOffsetToPosition(line, offset); } - /** - * @param line 1-based index - * @param offset 1-based index - */ - positionToLineOffset(position: number): ILineInfo { + positionToLineOffset(position: number): protocol.Location { return this.textStorage.positionToLineOffset(position); } diff --git a/src/server/scriptVersionCache.ts b/src/server/scriptVersionCache.ts index 464eea2efab..b6a189904db 100644 --- a/src/server/scriptVersionCache.ts +++ b/src/server/scriptVersionCache.ts @@ -8,15 +8,13 @@ namespace ts.server { export interface LineCollection { charCount(): number; lineCount(): number; - isLeaf(): boolean; + isLeaf(): this is LineLeaf; walk(rangeStart: number, rangeLength: number, walkFns: ILineIndexWalker): void; } - export interface ILineInfo { - line: number; - offset: number; - text?: string; - leaf?: LineLeaf; + export interface AbsolutePositionAndLineText { + absolutePosition: number; + lineText: string | undefined; } export enum CharRangeSection { @@ -97,22 +95,20 @@ namespace ts.server { } // path at least length two (root and leaf) - let insertionNode = this.startPath[this.startPath.length - 2]; const leafNode = this.startPath[this.startPath.length - 1]; - const len = lines.length; - if (len > 0) { + if (lines.length > 0) { leafNode.text = lines[0]; - if (len > 1) { - let insertedNodes = new Array(len - 1); + if (lines.length > 1) { + let insertedNodes = new Array(lines.length - 1); let startNode = leafNode; for (let i = 1; i < lines.length; i++) { insertedNodes[i - 1] = new LineLeaf(lines[i]); } let pathIndex = this.startPath.length - 2; while (pathIndex >= 0) { - insertionNode = this.startPath[pathIndex]; + const insertionNode = this.startPath[pathIndex]; insertedNodes = insertionNode.insertAt(startNode, insertedNodes); pathIndex--; startNode = insertionNode; @@ -134,6 +130,7 @@ namespace ts.server { } } else { + const insertionNode = this.startPath[this.startPath.length - 2]; // no content for leaf node, so delete it insertionNode.remove(leafNode); for (let j = this.startPath.length - 2; j >= 0; j--) { @@ -251,7 +248,7 @@ namespace ts.server { } getTextChangeRange() { - return ts.createTextChangeRange(ts.createTextSpan(this.pos, this.deleteLen), + return createTextChangeRange(createTextSpan(this.pos, this.deleteLen), this.insertedText ? this.insertedText.length : 0); } } @@ -281,9 +278,9 @@ namespace ts.server { // REVIEW: can optimize by coalescing simple edits edit(pos: number, deleteLen: number, insertedText?: string) { this.changes.push(new TextChange(pos, deleteLen, insertedText)); - if ((this.changes.length > ScriptVersionCache.changeNumberThreshold) || - (deleteLen > ScriptVersionCache.changeLengthThreshold) || - (insertedText && (insertedText.length > ScriptVersionCache.changeLengthThreshold))) { + if (this.changes.length > ScriptVersionCache.changeNumberThreshold || + deleteLen > ScriptVersionCache.changeLengthThreshold || + insertedText && insertedText.length > ScriptVersionCache.changeLengthThreshold) { this.getSnapshot(); } } @@ -340,21 +337,21 @@ namespace ts.server { getTextChangesBetweenVersions(oldVersion: number, newVersion: number) { if (oldVersion < newVersion) { if (oldVersion >= this.minVersion) { - const textChangeRanges: ts.TextChangeRange[] = []; + const textChangeRanges: TextChangeRange[] = []; for (let i = oldVersion + 1; i <= newVersion; i++) { const snap = this.versions[this.versionToIndex(i)]; for (const textChange of snap.changesSincePreviousVersion) { textChangeRanges.push(textChange.getTextChangeRange()); } } - return ts.collapseTextChangeRangesAcrossMultipleVersions(textChangeRanges); + return collapseTextChangeRangesAcrossMultipleVersions(textChangeRanges); } else { return undefined; } } else { - return ts.unchangedTextChangeRange; + return unchangedTextChangeRange; } } @@ -368,7 +365,7 @@ namespace ts.server { } } - export class LineIndexSnapshot implements ts.IScriptSnapshot { + export class LineIndexSnapshot implements IScriptSnapshot { constructor(readonly version: number, readonly cache: ScriptVersionCache, readonly index: LineIndex, readonly changesSincePreviousVersion: ReadonlyArray = emptyArray) { } @@ -380,10 +377,10 @@ namespace ts.server { return this.index.root.charCount(); } - getChangeRange(oldSnapshot: ts.IScriptSnapshot): ts.TextChangeRange { + getChangeRange(oldSnapshot: IScriptSnapshot): TextChangeRange { if (oldSnapshot instanceof LineIndexSnapshot && this.cache === oldSnapshot.cache) { if (this.version <= oldSnapshot.version) { - return ts.unchangedTextChangeRange; + return unchangedTextChangeRange; } else { return this.cache.getTextChangesBetweenVersions(oldSnapshot.version, this.version); @@ -397,22 +394,27 @@ namespace ts.server { // set this to true to check each edit for accuracy checkEdits = false; - charOffsetToLineNumberAndPos(charOffset: number) { - return this.root.charOffsetToLineNumberAndPos(1, charOffset); + absolutePositionOfStartOfLine(oneBasedLine: number): number { + return this.lineNumberToInfo(oneBasedLine).absolutePosition; } - lineNumberToInfo(lineNumber: number): ILineInfo { + positionToLineOffset(position: number): protocol.Location { + const { oneBasedLine, zeroBasedColumn } = this.root.charOffsetToLineInfo(1, position); + return { line: oneBasedLine, offset: zeroBasedColumn + 1 }; + } + + private positionToColumnAndLineText(position: number): { zeroBasedColumn: number, lineText: string } { + return this.root.charOffsetToLineInfo(1, position); + } + + lineNumberToInfo(oneBasedLine: number): AbsolutePositionAndLineText { const lineCount = this.root.lineCount(); - if (lineNumber <= lineCount) { - const lineInfo = this.root.lineNumberToInfo(lineNumber, 0); - lineInfo.line = lineNumber; - return lineInfo; + if (oneBasedLine <= lineCount) { + const { position, leaf } = this.root.lineNumberToInfo(oneBasedLine, 0); + return { absolutePosition: position, lineText: leaf && leaf.text }; } else { - return { - line: lineNumber, - offset: this.root.charCount() - }; + return { absolutePosition: this.root.charCount(), lineText: undefined }; } } @@ -468,12 +470,9 @@ namespace ts.server { return !walkFns.done; } - edit(pos: number, deleteLength: number, newText?: string) { - function editFlat(source: string, s: number, dl: number, nt = "") { - return source.substring(0, s) + nt + source.substring(s + dl, source.length); - } + edit(pos: number, deleteLength: number, newText?: string): LineIndex { if (this.root.charCount() === 0) { - // TODO: assert deleteLength === 0 + Debug.assert(deleteLength === 0); // Can't delete from empty document if (newText !== undefined) { this.load(LineIndex.linesFromText(newText).lines); return this; @@ -482,7 +481,8 @@ namespace ts.server { else { let checkText: string; if (this.checkEdits) { - checkText = editFlat(this.getText(0, this.root.charCount()), pos, deleteLength, newText); + const source = this.getText(0, this.root.charCount()); + checkText = source.slice(0, pos) + newText + source.slice(pos + deleteLength); } const walker = new EditWalker(); let suppressTrailingText = false; @@ -502,63 +502,44 @@ namespace ts.server { else if (deleteLength > 0) { // check whether last characters deleted are line break const e = pos + deleteLength; - const lineInfo = this.charOffsetToLineNumberAndPos(e); - if ((lineInfo && (lineInfo.offset === 0))) { + const { zeroBasedColumn, lineText } = this.positionToColumnAndLineText(e); + if (zeroBasedColumn === 0) { // move range end just past line that will merge with previous line - deleteLength += lineInfo.text.length; + deleteLength += lineText.length; // store text by appending to end of insertedText - if (newText) { - newText = newText + lineInfo.text; - } - else { - newText = lineInfo.text; - } + newText = newText ? newText + lineText : lineText; } } - if (pos < this.root.charCount()) { - this.root.walk(pos, deleteLength, walker); - walker.insertLines(newText, suppressTrailingText); - } + + this.root.walk(pos, deleteLength, walker); + walker.insertLines(newText, suppressTrailingText); + if (this.checkEdits) { - const updatedText = this.getText(0, this.root.charCount()); + const updatedText = walker.lineIndex.getText(0, walker.lineIndex.getLength()); Debug.assert(checkText === updatedText, "buffer edit mismatch"); } + return walker.lineIndex; } } - static buildTreeFromBottom(nodes: LineCollection[]): LineNode { - const nodeCount = Math.ceil(nodes.length / lineCollectionCapacity); - const interiorNodes: LineNode[] = []; + private static buildTreeFromBottom(nodes: LineCollection[]): LineNode { + if (nodes.length < lineCollectionCapacity) { + return new LineNode(nodes); + } + + const interiorNodes = new Array(Math.ceil(nodes.length / lineCollectionCapacity)); let nodeIndex = 0; - for (let i = 0; i < nodeCount; i++) { - interiorNodes[i] = new LineNode(); - let charCount = 0; - let lineCount = 0; - for (let j = 0; j < lineCollectionCapacity; j++) { - if (nodeIndex < nodes.length) { - interiorNodes[i].add(nodes[nodeIndex]); - charCount += nodes[nodeIndex].charCount(); - lineCount += nodes[nodeIndex].lineCount(); - } - else { - break; - } - nodeIndex++; - } - interiorNodes[i].totalChars = charCount; - interiorNodes[i].totalLines = lineCount; - } - if (interiorNodes.length === 1) { - return interiorNodes[0]; - } - else { - return this.buildTreeFromBottom(interiorNodes); + for (let i = 0; i < interiorNodes.length; i++) { + const end = Math.min(nodeIndex + lineCollectionCapacity, nodes.length); + interiorNodes[i] = new LineNode(nodes.slice(nodeIndex, end)); + nodeIndex = end; } + return this.buildTreeFromBottom(interiorNodes); } static linesFromText(text: string) { - const lineMap = ts.computeLineStarts(text); + const lineMap = computeLineStarts(text); if (lineMap.length === 0) { return { lines: [], lineMap }; @@ -583,7 +564,10 @@ namespace ts.server { export class LineNode implements LineCollection { totalChars = 0; totalLines = 0; - children: LineCollection[] = []; + + constructor(private readonly children: LineCollection[] = []) { + if (children.length) this.updateCounts(); + } isLeaf() { return false; @@ -598,7 +582,7 @@ namespace ts.server { } } - execWalk(rangeStart: number, rangeLength: number, walkFns: ILineIndexWalker, childIndex: number, nodeType: CharRangeSection) { + private execWalk(rangeStart: number, rangeLength: number, walkFns: ILineIndexWalker, childIndex: number, nodeType: CharRangeSection) { if (walkFns.pre) { walkFns.pre(rangeStart, rangeLength, this.children[childIndex], this, nodeType); } @@ -614,7 +598,7 @@ namespace ts.server { return walkFns.done; } - skipChild(relativeStart: number, relativeLength: number, childIndex: number, walkFns: ILineIndexWalker, nodeType: CharRangeSection) { + private skipChild(relativeStart: number, relativeLength: number, childIndex: number, walkFns: ILineIndexWalker, nodeType: CharRangeSection) { if (walkFns.pre && (!walkFns.done)) { walkFns.pre(relativeStart, relativeLength, this.children[childIndex], this, nodeType); walkFns.goSubtree = true; @@ -624,16 +608,14 @@ namespace ts.server { walk(rangeStart: number, rangeLength: number, walkFns: ILineIndexWalker) { // assume (rangeStart < this.totalChars) && (rangeLength <= this.totalChars) let childIndex = 0; - let child = this.children[0]; - let childCharCount = child.charCount(); + let childCharCount = this.children[childIndex].charCount(); // find sub-tree containing start let adjustedStart = rangeStart; while (adjustedStart >= childCharCount) { this.skipChild(adjustedStart, rangeLength, childIndex, walkFns, CharRangeSection.PreStart); adjustedStart -= childCharCount; childIndex++; - child = this.children[childIndex]; - childCharCount = child.charCount(); + childCharCount = this.children[childIndex].charCount(); } // Case I: both start and end of range in same subtree if ((adjustedStart + rangeLength) <= childCharCount) { @@ -648,7 +630,7 @@ namespace ts.server { } let adjustedLength = rangeLength - (childCharCount - adjustedStart); childIndex++; - child = this.children[childIndex]; + const child = this.children[childIndex]; childCharCount = child.charCount(); while (adjustedLength > childCharCount) { if (this.execWalk(0, childCharCount, walkFns, childIndex, CharRangeSection.Mid)) { @@ -656,8 +638,7 @@ namespace ts.server { } adjustedLength -= childCharCount; childIndex++; - child = this.children[childIndex]; - childCharCount = child.charCount(); + childCharCount = this.children[childIndex].charCount(); } if (adjustedLength > 0) { if (this.execWalk(0, adjustedLength, walkFns, childIndex, CharRangeSection.End)) { @@ -676,90 +657,88 @@ namespace ts.server { } } - charOffsetToLineNumberAndPos(lineNumber: number, charOffset: number): ILineInfo { - const childInfo = this.childFromCharOffset(lineNumber, charOffset); + // Input position is relative to the start of this node. + // Output line number is absolute. + charOffsetToLineInfo(lineNumberAccumulator: number, relativePosition: number): { oneBasedLine: number, zeroBasedColumn: number, lineText: string | undefined } { + const childInfo = this.childFromCharOffset(lineNumberAccumulator, relativePosition); if (!childInfo.child) { return { - line: lineNumber, - offset: charOffset, + oneBasedLine: lineNumberAccumulator, + zeroBasedColumn: relativePosition, + lineText: undefined, }; } else if (childInfo.childIndex < this.children.length) { if (childInfo.child.isLeaf()) { return { - line: childInfo.lineNumber, - offset: childInfo.charOffset, - text: ((childInfo.child)).text, - leaf: ((childInfo.child)) + oneBasedLine: childInfo.lineNumberAccumulator, + zeroBasedColumn: childInfo.relativePosition, + lineText: childInfo.child.text, }; } else { const lineNode = (childInfo.child); - return lineNode.charOffsetToLineNumberAndPos(childInfo.lineNumber, childInfo.charOffset); + return lineNode.charOffsetToLineInfo(childInfo.lineNumberAccumulator, childInfo.relativePosition); } } else { const lineInfo = this.lineNumberToInfo(this.lineCount(), 0); - return { line: this.lineCount(), offset: lineInfo.leaf.charCount() }; + return { oneBasedLine: this.lineCount(), zeroBasedColumn: lineInfo.leaf.charCount(), lineText: undefined }; } } - lineNumberToInfo(lineNumber: number, charOffset: number): ILineInfo { - const childInfo = this.childFromLineNumber(lineNumber, charOffset); + lineNumberToInfo(relativeOneBasedLine: number, positionAccumulator: number): { position: number, leaf: LineLeaf | undefined } { + const childInfo = this.childFromLineNumber(relativeOneBasedLine, positionAccumulator); if (!childInfo.child) { - return { - line: lineNumber, - offset: charOffset - }; + return { position: positionAccumulator, leaf: undefined }; } else if (childInfo.child.isLeaf()) { - return { - line: lineNumber, - offset: childInfo.charOffset, - text: ((childInfo.child)).text, - leaf: ((childInfo.child)) - }; + return { position: childInfo.positionAccumulator, leaf: childInfo.child }; } else { const lineNode = (childInfo.child); - return lineNode.lineNumberToInfo(childInfo.relativeLineNumber, childInfo.charOffset); + return lineNode.lineNumberToInfo(childInfo.relativeOneBasedLine, childInfo.positionAccumulator); } } - childFromLineNumber(lineNumber: number, charOffset: number) { + /** + * Input line number is relative to the start of this node. + * Output line number is relative to the child. + * positionAccumulator will be an absolute position once relativeLineNumber reaches 0. + */ + private childFromLineNumber(relativeOneBasedLine: number, positionAccumulator: number): { child: LineCollection, relativeOneBasedLine: number, positionAccumulator: number } { let child: LineCollection; - let relativeLineNumber = lineNumber; let i: number; - let len: number; - for (i = 0, len = this.children.length; i < len; i++) { + for (i = 0; i < this.children.length; i++) { child = this.children[i]; const childLineCount = child.lineCount(); - if (childLineCount >= relativeLineNumber) { + if (childLineCount >= relativeOneBasedLine) { break; } else { - relativeLineNumber -= childLineCount; - charOffset += child.charCount(); + relativeOneBasedLine -= childLineCount; + positionAccumulator += child.charCount(); } } - return { child, childIndex: i, relativeLineNumber, charOffset }; + return { child, relativeOneBasedLine, positionAccumulator }; } - childFromCharOffset(lineNumber: number, charOffset: number) { + private childFromCharOffset(lineNumberAccumulator: number, relativePosition: number + ): { child: LineCollection, childIndex: number, relativePosition: number, lineNumberAccumulator: number } { let child: LineCollection; let i: number; let len: number; for (i = 0, len = this.children.length; i < len; i++) { child = this.children[i]; - if (child.charCount() > charOffset) { + if (child.charCount() > relativePosition) { break; } else { - charOffset -= child.charCount(); - lineNumber += child.lineCount(); + relativePosition -= child.charCount(); + lineNumberAccumulator += child.lineCount(); } } - return { child, childIndex: i, charOffset, lineNumber }; + return { child, childIndex: i, relativePosition, lineNumberAccumulator }; } private splitAfter(childIndex: number) { diff --git a/src/server/server.ts b/src/server/server.ts index 873f7fc613c..f672ec31e1e 100644 --- a/src/server/server.ts +++ b/src/server/server.ts @@ -138,7 +138,7 @@ namespace ts.server { terminal: false, }); - class Logger implements ts.server.Logger { + class Logger implements server.Logger { private fd = -1; private seq = 0; private inGroup = false; @@ -526,12 +526,18 @@ namespace ts.server { if (err) { watchedFile.callback(watchedFile.fileName, FileWatcherEventKind.Changed); } - else if (watchedFile.mtime.getTime() !== stats.mtime.getTime()) { - watchedFile.mtime = stats.mtime; - const eventKind = watchedFile.mtime.getTime() === 0 - ? FileWatcherEventKind.Deleted - : FileWatcherEventKind.Changed; - watchedFile.callback(watchedFile.fileName, eventKind); + else { + const oldTime = watchedFile.mtime.getTime(); + const newTime = stats.mtime.getTime(); + if (oldTime !== newTime) { + watchedFile.mtime = stats.mtime; + const eventKind = oldTime === 0 + ? FileWatcherEventKind.Created + : newTime === 0 + ? FileWatcherEventKind.Deleted + : FileWatcherEventKind.Changed; + watchedFile.callback(watchedFile.fileName, eventKind); + } } }); } diff --git a/src/server/session.ts b/src/server/session.ts index a51e17cde65..7f1f571c629 100644 --- a/src/server/session.ts +++ b/src/server/session.ts @@ -49,7 +49,7 @@ namespace ts.server { interface FileStart { file: string; - start: ILineInfo; + start: protocol.Location; } function compareNumber(a: number, b: number) { @@ -72,28 +72,28 @@ namespace ts.server { } } - function formatDiag(fileName: NormalizedPath, project: Project, diag: ts.Diagnostic): protocol.Diagnostic { + function formatDiag(fileName: NormalizedPath, project: Project, diag: Diagnostic): protocol.Diagnostic { const scriptInfo = project.getScriptInfoForNormalizedPath(fileName); return { start: scriptInfo.positionToLineOffset(diag.start), end: scriptInfo.positionToLineOffset(diag.start + diag.length), - text: ts.flattenDiagnosticMessageText(diag.messageText, "\n"), + text: flattenDiagnosticMessageText(diag.messageText, "\n"), code: diag.code, category: DiagnosticCategory[diag.category].toLowerCase(), source: diag.source }; } - function convertToILineInfo(lineAndCharacter: LineAndCharacter): ILineInfo { + function convertToLocation(lineAndCharacter: LineAndCharacter): protocol.Location { return { line: lineAndCharacter.line + 1, offset: lineAndCharacter.character + 1 }; } - function formatConfigFileDiag(diag: ts.Diagnostic, includeFileName: true): protocol.DiagnosticWithFileName; - function formatConfigFileDiag(diag: ts.Diagnostic, includeFileName: false): protocol.Diagnostic; - function formatConfigFileDiag(diag: ts.Diagnostic, includeFileName: boolean): protocol.Diagnostic | protocol.DiagnosticWithFileName { - const start = diag.file && convertToILineInfo(getLineAndCharacterOfPosition(diag.file, diag.start)); - const end = diag.file && convertToILineInfo(getLineAndCharacterOfPosition(diag.file, diag.start + diag.length)); - const text = ts.flattenDiagnosticMessageText(diag.messageText, "\n"); + function formatConfigFileDiag(diag: Diagnostic, includeFileName: true): protocol.DiagnosticWithFileName; + function formatConfigFileDiag(diag: Diagnostic, includeFileName: false): protocol.Diagnostic; + function formatConfigFileDiag(diag: Diagnostic, includeFileName: boolean): protocol.Diagnostic | protocol.DiagnosticWithFileName { + const start = diag.file && convertToLocation(getLineAndCharacterOfPosition(diag.file, diag.start)); + const end = diag.file && convertToLocation(getLineAndCharacterOfPosition(diag.file, diag.start + diag.length)); + const text = flattenDiagnosticMessageText(diag.messageText, "\n"); const { code, source } = diag; const category = DiagnosticCategory[diag.category].toLowerCase(); return includeFileName ? { start, end, text, code, category, source, fileName: diag.file && diag.file.fileName } : @@ -389,8 +389,8 @@ namespace ts.server { this.host.write(formatMessage(msg, this.logger, this.byteLength, this.host.newLine)); } - public configFileDiagnosticEvent(triggerFile: string, configFile: string, diagnostics: ts.Diagnostic[]) { - const bakedDiags = ts.map(diagnostics, diagnostic => formatConfigFileDiag(diagnostic, /*includeFileName*/ true)); + public configFileDiagnosticEvent(triggerFile: string, configFile: string, diagnostics: Diagnostic[]) { + const bakedDiags = map(diagnostics, diagnostic => formatConfigFileDiag(diagnostic, /*includeFileName*/ true)); const ev: protocol.ConfigFileDiagnosticEvent = { seq: 0, type: "event", @@ -547,8 +547,8 @@ namespace ts.server { length: d.length, category: DiagnosticCategory[d.category].toLowerCase(), code: d.code, - startLocation: d.file && convertToILineInfo(getLineAndCharacterOfPosition(d.file, d.start)), - endLocation: d.file && convertToILineInfo(getLineAndCharacterOfPosition(d.file, d.start + d.length)) + startLocation: d.file && convertToLocation(getLineAndCharacterOfPosition(d.file, d.start)), + endLocation: d.file && convertToLocation(getLineAndCharacterOfPosition(d.file, d.start + d.length)) }); } @@ -607,7 +607,7 @@ namespace ts.server { return { file: def.fileName, start: defScriptInfo.positionToLineOffset(def.textSpan.start), - end: defScriptInfo.positionToLineOffset(ts.textSpanEnd(def.textSpan)) + end: defScriptInfo.positionToLineOffset(textSpanEnd(def.textSpan)) }; }); } @@ -631,7 +631,7 @@ namespace ts.server { return { file: def.fileName, start: defScriptInfo.positionToLineOffset(def.textSpan.start), - end: defScriptInfo.positionToLineOffset(ts.textSpanEnd(def.textSpan)) + end: defScriptInfo.positionToLineOffset(textSpanEnd(def.textSpan)) }; }); } @@ -649,7 +649,7 @@ namespace ts.server { return { file: fileName, start: scriptInfo.positionToLineOffset(textSpan.start), - end: scriptInfo.positionToLineOffset(ts.textSpanEnd(textSpan)) + end: scriptInfo.positionToLineOffset(textSpanEnd(textSpan)) }; }); } @@ -673,7 +673,7 @@ namespace ts.server { const { fileName, isWriteAccess, textSpan, isInString } = occurrence; const scriptInfo = project.getScriptInfo(fileName); const start = scriptInfo.positionToLineOffset(textSpan.start); - const end = scriptInfo.positionToLineOffset(ts.textSpanEnd(textSpan)); + const end = scriptInfo.positionToLineOffset(textSpanEnd(textSpan)); const result: protocol.OccurrencesResponseItem = { start, end, @@ -723,7 +723,7 @@ namespace ts.server { return documentHighlights; } - function convertToDocumentHighlightsItem(documentHighlights: ts.DocumentHighlights): ts.server.protocol.DocumentHighlightsItem { + function convertToDocumentHighlightsItem(documentHighlights: DocumentHighlights): protocol.DocumentHighlightsItem { const { fileName, highlightSpans } = documentHighlights; const scriptInfo = project.getScriptInfo(fileName); @@ -732,10 +732,10 @@ namespace ts.server { highlightSpans: highlightSpans.map(convertHighlightSpan) }; - function convertHighlightSpan(highlightSpan: ts.HighlightSpan): ts.server.protocol.HighlightSpan { + function convertHighlightSpan(highlightSpan: HighlightSpan): protocol.HighlightSpan { const { textSpan, kind } = highlightSpan; const start = scriptInfo.positionToLineOffset(textSpan.start); - const end = scriptInfo.positionToLineOffset(ts.textSpanEnd(textSpan)); + const end = scriptInfo.positionToLineOffset(textSpanEnd(textSpan)); return { start, end, kind }; } } @@ -778,7 +778,7 @@ namespace ts.server { const scriptInfo = this.projectService.getScriptInfo(args.file); projects = scriptInfo.containingProjects; } - // ts.filter handles case when 'projects' is undefined + // filter handles case when 'projects' is undefined projects = filter(projects, p => p.languageServiceEnabled); if (!projects || !projects.length) { return Errors.ThrowNoProject(); @@ -831,28 +831,29 @@ namespace ts.server { return { file: location.fileName, start: locationScriptInfo.positionToLineOffset(location.textSpan.start), - end: locationScriptInfo.positionToLineOffset(ts.textSpanEnd(location.textSpan)), + end: locationScriptInfo.positionToLineOffset(textSpanEnd(location.textSpan)), }; }); }, compareRenameLocation, (a, b) => a.file === b.file && a.start.line === b.start.line && a.start.offset === b.start.offset ); - const locs = fileSpans.reduce((accum, cur) => { + + const locs: protocol.SpanGroup[] = []; + for (const cur of fileSpans) { let curFileAccum: protocol.SpanGroup; - if (accum.length > 0) { - curFileAccum = accum[accum.length - 1]; + if (locs.length > 0) { + curFileAccum = locs[locs.length - 1]; if (curFileAccum.file !== cur.file) { curFileAccum = undefined; } } if (!curFileAccum) { curFileAccum = { file: cur.file, locs: [] }; - accum.push(curFileAccum); + locs.push(curFileAccum); } curFileAccum.locs.push({ start: cur.start, end: cur.end }); - return accum; - }, []); + } return { info: renameInfo, locs }; } @@ -912,10 +913,10 @@ namespace ts.server { return undefined; } - const displayString = ts.displayPartsToString(nameInfo.displayParts); + const displayString = displayPartsToString(nameInfo.displayParts); const nameSpan = nameInfo.textSpan; const nameColStart = scriptInfo.positionToLineOffset(nameSpan.start).offset; - const nameText = scriptInfo.getSnapshot().getText(nameSpan.start, ts.textSpanEnd(nameSpan)); + const nameText = scriptInfo.getSnapshot().getText(nameSpan.start, textSpanEnd(nameSpan)); const refs = combineProjectOutput( projects, (project: Project) => { @@ -928,12 +929,12 @@ namespace ts.server { const refScriptInfo = project.getScriptInfo(ref.fileName); const start = refScriptInfo.positionToLineOffset(ref.textSpan.start); const refLineSpan = refScriptInfo.lineToTextSpan(start.line - 1); - const lineText = refScriptInfo.getSnapshot().getText(refLineSpan.start, ts.textSpanEnd(refLineSpan)).replace(/\r|\n/g, ""); + const lineText = refScriptInfo.getSnapshot().getText(refLineSpan.start, textSpanEnd(refLineSpan)).replace(/\r|\n/g, ""); return { file: ref.fileName, start, lineText, - end: refScriptInfo.positionToLineOffset(ts.textSpanEnd(ref.textSpan)), + end: refScriptInfo.positionToLineOffset(textSpanEnd(ref.textSpan)), isWriteAccess: ref.isWriteAccess, isDefinition: ref.isDefinition }; @@ -1056,14 +1057,14 @@ namespace ts.server { } if (simplifiedResult) { - const displayString = ts.displayPartsToString(quickInfo.displayParts); - const docString = ts.displayPartsToString(quickInfo.documentation); + const displayString = displayPartsToString(quickInfo.displayParts); + const docString = displayPartsToString(quickInfo.documentation); return { kind: quickInfo.kind, kindModifiers: quickInfo.kindModifiers, start: scriptInfo.positionToLineOffset(quickInfo.textSpan.start), - end: scriptInfo.positionToLineOffset(ts.textSpanEnd(quickInfo.textSpan)), + end: scriptInfo.positionToLineOffset(textSpanEnd(quickInfo.textSpan)), displayString, documentation: docString, tags: quickInfo.tags || [] @@ -1123,32 +1124,29 @@ namespace ts.server { // only to the previous line. If all this is true, then // add edits necessary to properly indent the current line. if ((args.key === "\n") && ((!edits) || (edits.length === 0) || allEditsBeforePos(edits, position))) { - const lineInfo = scriptInfo.getLineInfo(args.line); - if (lineInfo && (lineInfo.leaf) && (lineInfo.leaf.text)) { - const lineText = lineInfo.leaf.text; - if (lineText.search("\\S") < 0) { - const preferredIndent = project.getLanguageService(/*ensureSynchronized*/ false).getIndentationAtPosition(file, position, formatOptions); - let hasIndent = 0; - let i: number, len: number; - for (i = 0, len = lineText.length; i < len; i++) { - if (lineText.charAt(i) === " ") { - hasIndent++; - } - else if (lineText.charAt(i) === "\t") { - hasIndent += formatOptions.tabSize; - } - else { - break; - } + const { lineText, absolutePosition } = scriptInfo.getLineInfo(args.line); + if (lineText && lineText.search("\\S") < 0) { + const preferredIndent = project.getLanguageService(/*ensureSynchronized*/ false).getIndentationAtPosition(file, position, formatOptions); + let hasIndent = 0; + let i: number, len: number; + for (i = 0, len = lineText.length; i < len; i++) { + if (lineText.charAt(i) === " ") { + hasIndent++; } - // i points to the first non whitespace character - if (preferredIndent !== hasIndent) { - const firstNoWhiteSpacePosition = lineInfo.offset + i; - edits.push({ - span: ts.createTextSpanFromBounds(lineInfo.offset, firstNoWhiteSpacePosition), - newText: formatting.getIndentationString(preferredIndent, formatOptions) - }); + else if (lineText.charAt(i) === "\t") { + hasIndent += formatOptions.tabSize; } + else { + break; + } + } + // i points to the first non whitespace character + if (preferredIndent !== hasIndent) { + const firstNoWhiteSpacePosition = absolutePosition + i; + edits.push({ + span: createTextSpanFromBounds(absolutePosition, firstNoWhiteSpacePosition), + newText: formatting.getIndentationString(preferredIndent, formatOptions) + }); } } } @@ -1160,7 +1158,7 @@ namespace ts.server { return edits.map((edit) => { return { start: scriptInfo.positionToLineOffset(edit.span.start), - end: scriptInfo.positionToLineOffset(ts.textSpanEnd(edit.span)), + end: scriptInfo.positionToLineOffset(textSpanEnd(edit.span)), newText: edit.newText ? edit.newText : "" }; }); @@ -1178,15 +1176,13 @@ namespace ts.server { return undefined; } if (simplifiedResult) { - return completions.entries.reduce((result: protocol.CompletionEntry[], entry: ts.CompletionEntry) => { + return mapDefined(completions.entries, entry => { if (completions.isMemberCompletion || (entry.name.toLowerCase().indexOf(prefix.toLowerCase()) === 0)) { const { name, kind, kindModifiers, sortText, replacementSpan } = entry; - const convertedSpan: protocol.TextSpan = - replacementSpan ? this.decorateSpan(replacementSpan, scriptInfo) : undefined; - result.push({ name, kind, kindModifiers, sortText, replacementSpan: convertedSpan }); + const convertedSpan = replacementSpan ? this.decorateSpan(replacementSpan, scriptInfo) : undefined; + return { name, kind, kindModifiers, sortText, replacementSpan: convertedSpan }; } - return result; - }, []).sort((a, b) => ts.compareStrings(a.name, b.name)); + }).sort((a, b) => compareStrings(a.name, b.name)); } else { return completions; @@ -1198,13 +1194,8 @@ namespace ts.server { const scriptInfo = project.getScriptInfoForNormalizedPath(file); const position = this.getPosition(args, scriptInfo); - return args.entryNames.reduce((accum: protocol.CompletionEntryDetails[], entryName: string) => { - const details = project.getLanguageService().getCompletionEntryDetails(file, position, entryName); - if (details) { - accum.push(details); - } - return accum; - }, []); + return mapDefined(args.entryNames, entryName => + project.getLanguageService().getCompletionEntryDetails(file, position, entryName)); } private getCompileOnSaveAffectedFileList(args: protocol.FileRequestArgs): protocol.CompileOnSaveAffectedFileListSingleProject[] { @@ -1269,14 +1260,11 @@ namespace ts.server { } private getDiagnostics(next: NextStep, delay: number, fileNames: string[]): void { - const checkList = fileNames.reduce((accum: PendingErrorCheck[], uncheckedFileName: string) => { + const checkList = mapDefined(fileNames, uncheckedFileName => { const fileName = toNormalizedPath(uncheckedFileName); const project = this.projectService.getDefaultProjectForFile(fileName, /*refreshInferredProjects*/ true); - if (project) { - accum.push({ fileName, project }); - } - return accum; - }, []); + return project && { fileName, project }; + }); if (checkList.length > 0) { this.updateErrorCheck(next, checkList, this.changeSeq, (n) => n === this.changeSeq, delay); @@ -1321,11 +1309,11 @@ namespace ts.server { if (!fileName) { return; } - const file = ts.normalizePath(fileName); + const file = normalizePath(fileName); this.projectService.closeClientFile(file); } - private decorateNavigationBarItems(items: ts.NavigationBarItem[], scriptInfo: ScriptInfo): protocol.NavigationBarItem[] { + private decorateNavigationBarItems(items: NavigationBarItem[], scriptInfo: ScriptInfo): protocol.NavigationBarItem[] { return map(items, item => ({ text: item.text, kind: item.kind, @@ -1346,7 +1334,7 @@ namespace ts.server { : items; } - private decorateNavigationTree(tree: ts.NavigationTree, scriptInfo: ScriptInfo): protocol.NavigationTree { + private decorateNavigationTree(tree: NavigationTree, scriptInfo: ScriptInfo): protocol.NavigationTree { return { text: tree.text, kind: tree.kind, @@ -1359,7 +1347,7 @@ namespace ts.server { private decorateSpan(span: TextSpan, scriptInfo: ScriptInfo): protocol.TextSpan { return { start: scriptInfo.positionToLineOffset(span.start), - end: scriptInfo.positionToLineOffset(ts.textSpanEnd(span)) + end: scriptInfo.positionToLineOffset(textSpanEnd(span)) }; } @@ -1389,7 +1377,7 @@ namespace ts.server { return navItems.map((navItem) => { const scriptInfo = project.getScriptInfo(navItem.fileName); const start = scriptInfo.positionToLineOffset(navItem.textSpan.start); - const end = scriptInfo.positionToLineOffset(ts.textSpanEnd(navItem.textSpan)); + const end = scriptInfo.positionToLineOffset(textSpanEnd(navItem.textSpan)); const bakedItem: protocol.NavtoItem = { name: navItem.name, kind: navItem.kind, @@ -1454,7 +1442,7 @@ namespace ts.server { } private getSupportedCodeFixes(): string[] { - return ts.getSupportedCodeFixes(); + return getSupportedCodeFixes(); } private isLocation(locationOrSpan: protocol.FileLocationOrRangeRequestArgs): locationOrSpan is protocol.FileLocationRequestArgs { @@ -1485,7 +1473,7 @@ namespace ts.server { return project.getLanguageService().getApplicableRefactors(file, position || textRange); } - private getEditsForRefactor(args: protocol.GetEditsForRefactorRequestArgs, simplifiedResult: boolean): ts.RefactorEditInfo | protocol.RefactorEditInfo { + private getEditsForRefactor(args: protocol.GetEditsForRefactorRequestArgs, simplifiedResult: boolean): RefactorEditInfo | protocol.RefactorEditInfo { const { file, project } = this.getFileAndProjectWithoutRefreshingInferredProjects(args); const scriptInfo = project.getScriptInfoForNormalizedPath(file); const { position, textRange } = this.extractPositionAndRange(args, scriptInfo); @@ -1506,7 +1494,7 @@ namespace ts.server { if (simplifiedResult) { const file = result.renameFilename; - let location: ILineInfo | undefined = undefined; + let location: protocol.Location | undefined; if (file !== undefined && result.renameLocation !== undefined) { const renameScriptInfo = project.getScriptInfoForNormalizedPath(toNormalizedPath(file)); location = renameScriptInfo.positionToLineOffset(result.renameLocation); @@ -1654,7 +1642,7 @@ namespace ts.server { getCanonicalFileName(fileName: string) { const name = this.host.useCaseSensitiveFileNames ? fileName : fileName.toLowerCase(); - return ts.normalizePath(name); + return normalizePath(name); } exit() { diff --git a/src/server/types.ts b/src/server/types.ts index 81e1f09639f..72d6a7c6cfc 100644 --- a/src/server/types.ts +++ b/src/server/types.ts @@ -31,9 +31,9 @@ declare namespace ts.server { export interface DiscoverTypings extends TypingInstallerRequest { readonly fileNames: string[]; - readonly projectRootPath: ts.Path; - readonly compilerOptions: ts.CompilerOptions; - readonly typeAcquisition: ts.TypeAcquisition; + readonly projectRootPath: Path; + readonly compilerOptions: CompilerOptions; + readonly typeAcquisition: TypeAcquisition; readonly unresolvedImports: SortedReadonlyArray; readonly cachePath?: string; readonly kind: "discover"; @@ -63,8 +63,8 @@ declare namespace ts.server { } export interface SetTypings extends ProjectResponse { - readonly typeAcquisition: ts.TypeAcquisition; - readonly compilerOptions: ts.CompilerOptions; + readonly typeAcquisition: TypeAcquisition; + readonly compilerOptions: CompilerOptions; readonly typings: string[]; readonly unresolvedImports: SortedReadonlyArray; readonly kind: ActionSet; diff --git a/src/server/typingsInstaller/typingsInstaller.ts b/src/server/typingsInstaller/typingsInstaller.ts index 71ad0171bf3..730257d2ccc 100644 --- a/src/server/typingsInstaller/typingsInstaller.ts +++ b/src/server/typingsInstaller/typingsInstaller.ts @@ -145,6 +145,7 @@ namespace ts.server.typingsInstaller { const discoverTypingsResult = JsTyping.discoverTypings( this.installTypingHost, + this.log.isEnabled() ? this.log.writeLine : undefined, req.fileNames, req.projectRootPath, this.safeListPath, diff --git a/src/server/utilities.ts b/src/server/utilities.ts index 416b148fc0d..3949e7b58dd 100644 --- a/src/server/utilities.ts +++ b/src/server/utilities.ts @@ -77,7 +77,7 @@ namespace ts.server { tabSize: 4, newLineCharacter: host.newLine || "\n", convertTabsToSpaces: true, - indentStyle: ts.IndentStyle.Smart, + indentStyle: IndentStyle.Smart, insertSpaceAfterConstructor: false, insertSpaceAfterCommaDelimiter: true, insertSpaceAfterSemicolonInForStatements: true, @@ -196,7 +196,7 @@ namespace ts.server { } export function enumerateInsertsAndDeletes(a: SortedReadonlyArray, b: SortedReadonlyArray, inserted: (item: T) => void, deleted: (item: T) => void, compare?: (a: T, b: T) => Comparison) { - compare = compare || ts.compareValues; + compare = compare || compareValues; let aIndex = 0; let bIndex = 0; const aLen = a.length; diff --git a/src/services/codefixes/fixUnusedIdentifier.ts b/src/services/codefixes/fixUnusedIdentifier.ts index cbe2ba5b1c5..18491aaba26 100644 --- a/src/services/codefixes/fixUnusedIdentifier.ts +++ b/src/services/codefixes/fixUnusedIdentifier.ts @@ -25,15 +25,15 @@ namespace ts.codefix { return [deleteNode(token.parent)]; default: - return [deleteDefault()]; + return deleteDefault(); } - function deleteDefault() { + function deleteDefault(): CodeAction[] | undefined { if (isDeclarationName(token)) { - return deleteNode(token.parent); + return [deleteNode(token.parent)]; } else if (isLiteralComputedPropertyDeclarationName(token)) { - return deleteNode(token.parent.parent); + return [deleteNode(token.parent.parent)]; } else { return undefined; @@ -87,20 +87,16 @@ namespace ts.codefix { case SyntaxKind.ImportSpecifier: const namedImports = parent.parent; if (namedImports.elements.length === 1) { - // Only 1 import and it is unused. So the entire declaration should be removed. - const importSpec = getAncestor(identifier, SyntaxKind.ImportDeclaration); - return [deleteNode(importSpec)]; + return deleteNamedImportBinding(namedImports); } else { // delete import specifier return [deleteNodeInList(parent)]; } - // handle case where "import d, * as ns from './file'" - // or "'import {a, b as ns} from './file'" case SyntaxKind.ImportClause: // this covers both 'import |d|' and 'import |d,| *' const importClause = parent; - if (!importClause.namedBindings) { // |import d from './file'| or |import * as ns from './file'| + if (!importClause.namedBindings) { // |import d from './file'| const importDecl = getAncestor(importClause, SyntaxKind.ImportDeclaration); return [deleteNode(importDecl)]; } @@ -118,22 +114,30 @@ namespace ts.codefix { } case SyntaxKind.NamespaceImport: - const namespaceImport = parent; - if (namespaceImport.name === identifier && !(namespaceImport.parent).name) { - const importDecl = getAncestor(namespaceImport, SyntaxKind.ImportDeclaration); - return [deleteNode(importDecl)]; - } - else { - const previousToken = getTokenAtPosition(sourceFile, namespaceImport.pos - 1, /*includeJsDocComment*/ false); - if (previousToken && previousToken.kind === SyntaxKind.CommaToken) { - const startPosition = textChanges.getAdjustedStartPosition(sourceFile, previousToken, {}, textChanges.Position.FullStart); - return [deleteRange({ pos: startPosition, end: namespaceImport.end })]; - } - return [deleteRange(namespaceImport)]; - } + return deleteNamedImportBinding(parent); default: - return [deleteDefault()]; + return deleteDefault(); + } + } + + function deleteNamedImportBinding(namedBindings: NamedImportBindings): CodeAction[] | undefined { + if ((namedBindings.parent).name) { + // Delete named imports while preserving the default import + // import d|, * as ns| from './file' + // import d|, { a }| from './file' + const previousToken = getTokenAtPosition(sourceFile, namedBindings.pos - 1, /*includeJsDocComment*/ false); + if (previousToken && previousToken.kind === SyntaxKind.CommaToken) { + return [deleteRange({ pos: previousToken.getStart(), end: namedBindings.end })]; + } + return undefined; + } + else { + // Delete the entire import declaration + // |import * as ns from './file'| + // |import { a } from './file'| + const importDecl = getAncestor(namedBindings, SyntaxKind.ImportDeclaration); + return [deleteNode(importDecl)]; } } diff --git a/src/services/codefixes/importFixes.ts b/src/services/codefixes/importFixes.ts index 0fa0b72162b..d30997aa076 100644 --- a/src/services/codefixes/importFixes.ts +++ b/src/services/codefixes/importFixes.ts @@ -222,10 +222,7 @@ namespace ts.codefix { } function getUniqueSymbolId(symbol: Symbol) { - if (symbol.flags & SymbolFlags.Alias) { - return getSymbolId(checker.getAliasedSymbol(symbol)); - } - return getSymbolId(symbol); + return getSymbolId(skipAlias(symbol, checker)); } function checkSymbolHasMeaning(symbol: Symbol, meaning: SemanticMeaning) { diff --git a/src/services/completions.ts b/src/services/completions.ts index 94e8779d1d7..12796a9b791 100644 --- a/src/services/completions.ts +++ b/src/services/completions.ts @@ -57,11 +57,11 @@ namespace ts.Completions { if (isSourceFileJavaScript(sourceFile)) { const uniqueNames = getCompletionEntriesFromSymbols(symbols, entries, location, /*performCharacterChecks*/ true, typeChecker, compilerOptions.target, log); - addRange(entries, getJavaScriptCompletionEntries(sourceFile, location.pos, uniqueNames, compilerOptions.target)); + getJavaScriptCompletionEntries(sourceFile, location.pos, uniqueNames, compilerOptions.target, entries); } else { if ((!symbols || symbols.length === 0) && keywordFilters === KeywordCompletionFilters.None) { - return undefined; + return undefined; } getCompletionEntriesFromSymbols(symbols, entries, location, /*performCharacterChecks*/ true, typeChecker, compilerOptions.target, log); @@ -79,40 +79,41 @@ namespace ts.Completions { return { isGlobalCompletion, isMemberCompletion, isNewIdentifierLocation, entries }; } - function getJavaScriptCompletionEntries(sourceFile: SourceFile, position: number, uniqueNames: Map, target: ScriptTarget): CompletionEntry[] { - const entries: CompletionEntry[] = []; - - const nameTable = getNameTable(sourceFile); - nameTable.forEach((pos, name) => { + function getJavaScriptCompletionEntries( + sourceFile: SourceFile, + position: number, + uniqueNames: Map, + target: ScriptTarget, + entries: Push): void { + getNameTable(sourceFile).forEach((pos, name) => { // Skip identifiers produced only from the current location if (pos === position) { return; } const realName = unescapeLeadingUnderscores(name); - if (!uniqueNames.get(realName)) { - uniqueNames.set(realName, true); - const displayName = getCompletionEntryDisplayName(realName, target, /*performCharacterChecks*/ true); - if (displayName) { - const entry = { - name: displayName, - kind: ScriptElementKind.warning, - kindModifiers: "", - sortText: "1" - }; - entries.push(entry); - } + if (uniqueNames.has(realName)) { + return; + } + + uniqueNames.set(realName, true); + const displayName = getCompletionEntryDisplayName(realName, target, /*performCharacterChecks*/ true); + if (displayName) { + entries.push({ + name: displayName, + kind: ScriptElementKind.warning, + kindModifiers: "", + sortText: "1" + }); } }); - - return entries; } function createCompletionEntry(symbol: Symbol, location: Node, performCharacterChecks: boolean, typeChecker: TypeChecker, target: ScriptTarget): CompletionEntry { // Try to get a valid display name for this symbol, if we could not find one, then ignore it. // We would like to only show things that can be added after a dot, so for instance numeric properties can // not be accessed with a dot (a.1 <- invalid) - const displayName = getCompletionEntryDisplayNameForSymbol(typeChecker, symbol, target, performCharacterChecks, location); + const displayName = getCompletionEntryDisplayNameForSymbol(symbol, target, performCharacterChecks); if (!displayName) { return undefined; } @@ -141,7 +142,7 @@ namespace ts.Completions { const entry = createCompletionEntry(symbol, location, performCharacterChecks, typeChecker, target); if (entry) { const id = entry.name; - if (!uniqueNames.get(id)) { + if (!uniqueNames.has(id)) { entries.push(entry); uniqueNames.set(id, true); } @@ -307,7 +308,7 @@ namespace ts.Completions { // We don't need to perform character checks here because we're only comparing the // name against 'entryName' (which is known to be good), not building a new // completion entry. - const symbol = forEach(symbols, s => getCompletionEntryDisplayNameForSymbol(typeChecker, s, compilerOptions.target, /*performCharacterChecks*/ false, location) === entryName ? s : undefined); + const symbol = forEach(symbols, s => getCompletionEntryDisplayNameForSymbol(s, compilerOptions.target, /*performCharacterChecks*/ false) === entryName ? s : undefined); if (symbol) { const { displayParts, documentation, symbolKind, tags } = SymbolDisplay.getSymbolDisplayPartsDocumentationAndSymbolKind(typeChecker, symbol, sourceFile, location, location, SemanticMeaning.All); @@ -341,20 +342,14 @@ namespace ts.Completions { return undefined; } - export function getCompletionEntrySymbol(typeChecker: TypeChecker, log: (message: string) => void, compilerOptions: CompilerOptions, sourceFile: SourceFile, position: number, entryName: string): Symbol { + export function getCompletionEntrySymbol(typeChecker: TypeChecker, log: (message: string) => void, compilerOptions: CompilerOptions, sourceFile: SourceFile, position: number, entryName: string): Symbol | undefined { // Compute all the completion symbols again. const completionData = getCompletionData(typeChecker, log, sourceFile, position); - if (completionData) { - const { symbols, location } = completionData; - - // Find the symbol with the matching entry name. - // We don't need to perform character checks here because we're only comparing the - // name against 'entryName' (which is known to be good), not building a new - // completion entry. - return forEach(symbols, s => getCompletionEntryDisplayNameForSymbol(typeChecker, s, compilerOptions.target, /*performCharacterChecks*/ false, location) === entryName ? s : undefined); - } - - return undefined; + // Find the symbol with the matching entry name. + // We don't need to perform character checks here because we're only comparing the + // name against 'entryName' (which is known to be good), not building a new + // completion entry. + return completionData && forEach(completionData.symbols, s => getCompletionEntryDisplayNameForSymbol(s, compilerOptions.target, /*performCharacterChecks*/ false) === entryName ? s : undefined); } interface CompletionData { @@ -369,7 +364,7 @@ namespace ts.Completions { } type Request = { kind: "JsDocTagName" } | { kind: "JsDocTag" } | { kind: "JsDocParameterName", tag: JSDocParameterTag }; - function getCompletionData(typeChecker: TypeChecker, log: (message: string) => void, sourceFile: SourceFile, position: number): CompletionData { + function getCompletionData(typeChecker: TypeChecker, log: (message: string) => void, sourceFile: SourceFile, position: number): CompletionData | undefined { const isJavaScriptFile = isSourceFileJavaScript(sourceFile); let request: Request | undefined; @@ -601,58 +596,60 @@ namespace ts.Completions { isNewIdentifierLocation = false; // Since this is qualified name check its a type node location - const isTypeLocation = isPartOfTypeNode(node.parent) || insideJsDocTagTypeExpression; + const isTypeLocation = insideJsDocTagTypeExpression || isPartOfTypeNode(node.parent); const isRhsOfImportDeclaration = isInRightSideOfInternalImportEqualsDeclaration(node); - if (node.kind === SyntaxKind.Identifier || node.kind === SyntaxKind.QualifiedName || node.kind === SyntaxKind.PropertyAccessExpression) { + if (isEntityName(node)) { let symbol = typeChecker.getSymbolAtLocation(node); + if (symbol) { + symbol = skipAlias(symbol, typeChecker); - // This is an alias, follow what it aliases - if (symbol && symbol.flags & SymbolFlags.Alias) { - symbol = typeChecker.getAliasedSymbol(symbol); - } - - if (symbol && symbol.flags & SymbolFlags.HasExports) { - // Extract module or enum members - const exportedSymbols = typeChecker.getExportsOfModule(symbol); - const isValidValueAccess = (symbol: Symbol) => typeChecker.isValidPropertyAccess((node.parent), symbol.getUnescapedName()); - const isValidTypeAccess = (symbol: Symbol) => symbolCanbeReferencedAtTypeLocation(symbol); - const isValidAccess = isRhsOfImportDeclaration ? - // Any kind is allowed when dotting off namespace in internal import equals declaration - (symbol: Symbol) => isValidTypeAccess(symbol) || isValidValueAccess(symbol) : - isTypeLocation ? isValidTypeAccess : isValidValueAccess; - forEach(exportedSymbols, symbol => { - if (isValidAccess(symbol)) { - symbols.push(symbol); + if (symbol.flags & (SymbolFlags.Module | SymbolFlags.Enum)) { + // Extract module or enum members + const exportedSymbols = typeChecker.getExportsOfModule(symbol); + const isValidValueAccess = (symbol: Symbol) => typeChecker.isValidPropertyAccess((node.parent), symbol.getUnescapedName()); + const isValidTypeAccess = (symbol: Symbol) => symbolCanBeReferencedAtTypeLocation(symbol); + const isValidAccess = isRhsOfImportDeclaration ? + // Any kind is allowed when dotting off namespace in internal import equals declaration + (symbol: Symbol) => isValidTypeAccess(symbol) || isValidValueAccess(symbol) : + isTypeLocation ? isValidTypeAccess : isValidValueAccess; + for (const symbol of exportedSymbols) { + if (isValidAccess(symbol)) { + symbols.push(symbol); + } } - }); + + // If the module is merged with a value, we must get the type of the class and add its propertes (for inherited static methods). + if (!isTypeLocation && symbol.declarations.some(d => d.kind !== SyntaxKind.SourceFile && d.kind !== SyntaxKind.ModuleDeclaration && d.kind !== SyntaxKind.EnumDeclaration)) { + addTypeProperties(typeChecker.getTypeOfSymbolAtLocation(symbol, node)); + } + + return; + } } } if (!isTypeLocation) { - const type = typeChecker.getTypeAtLocation(node); - addTypeProperties(type); + addTypeProperties(typeChecker.getTypeAtLocation(node)); } } function addTypeProperties(type: Type) { - if (type) { - // Filter private properties - for (const symbol of type.getApparentProperties()) { - if (typeChecker.isValidPropertyAccess((node.parent), symbol.getUnescapedName())) { - symbols.push(symbol); - } + // Filter private properties + for (const symbol of type.getApparentProperties()) { + if (typeChecker.isValidPropertyAccess((node.parent), symbol.getUnescapedName())) { + symbols.push(symbol); } + } - if (isJavaScriptFile && type.flags & TypeFlags.Union) { - // In javascript files, for union types, we don't just get the members that - // the individual types have in common, we also include all the members that - // each individual type has. This is because we're going to add all identifiers - // anyways. So we might as well elevate the members that were at least part - // of the individual types to a higher status since we know what they are. - const unionType = type; - for (const elementType of unionType.types) { - addTypeProperties(elementType); - } + if (isJavaScriptFile && type.flags & TypeFlags.Union) { + // In javascript files, for union types, we don't just get the members that + // the individual types have in common, we also include all the members that + // each individual type has. This is because we're going to add all identifiers + // anyways. So we might as well elevate the members that were at least part + // of the individual types to a higher status since we know what they are. + const unionType = type; + for (const elementType of unionType.types) { + addTypeProperties(elementType); } } } @@ -777,12 +774,12 @@ namespace ts.Completions { (!isContextTokenValueLocation(contextToken) && (isPartOfTypeNode(location) || isContextTokenTypeLocation(contextToken)))) { // Its a type, but you can reach it by namespace.type as well - return symbolCanbeReferencedAtTypeLocation(symbol); + return symbolCanBeReferencedAtTypeLocation(symbol); } } // expressions are value space (which includes the value namespaces) - return !!(symbol.flags & SymbolFlags.Value); + return !!(getCombinedLocalAndExportSymbolFlags(symbol) & SymbolFlags.Value); }); } @@ -812,21 +809,21 @@ namespace ts.Completions { } } - function symbolCanbeReferencedAtTypeLocation(symbol: Symbol): boolean { + function symbolCanBeReferencedAtTypeLocation(symbol: Symbol): boolean { + symbol = symbol.exportSymbol || symbol; + // This is an alias, follow what it aliases - if (symbol && symbol.flags & SymbolFlags.Alias) { - symbol = typeChecker.getAliasedSymbol(symbol); - } + symbol = skipAlias(symbol, typeChecker); if (symbol.flags & SymbolFlags.Type) { return true; } - if (symbol.flags & (SymbolFlags.ValueModule | SymbolFlags.NamespaceModule)) { + if (symbol.flags & SymbolFlags.Module) { const exportedSymbols = typeChecker.getExportsOfModule(symbol); // If the exported symbols contains type, // symbol can be referenced at locations where type is allowed - return forEach(exportedSymbols, symbolCanbeReferencedAtTypeLocation); + return forEach(exportedSymbols, symbolCanBeReferencedAtTypeLocation); } } @@ -1598,22 +1595,23 @@ namespace ts.Completions { /** * Get the name to be display in completion from a given symbol. * - * @return undefined if the name is of external module otherwise a name with striped of any quote + * @return undefined if the name is of external module */ - function getCompletionEntryDisplayNameForSymbol(typeChecker: TypeChecker, symbol: Symbol, target: ScriptTarget, performCharacterChecks: boolean, location: Node): string { - const displayName: string = getDeclaredName(typeChecker, symbol, location); + function getCompletionEntryDisplayNameForSymbol(symbol: Symbol, target: ScriptTarget, performCharacterChecks: boolean): string | undefined { + const name = symbol.getUnescapedName(); + if (!name) return undefined; - if (displayName) { - const firstCharCode = displayName.charCodeAt(0); - // First check of the displayName is not external module; if it is an external module, it is not valid entry - if ((symbol.flags & SymbolFlags.Namespace) && (firstCharCode === CharacterCodes.singleQuote || firstCharCode === CharacterCodes.doubleQuote)) { + // First check of the displayName is not external module; if it is an external module, it is not valid entry + if (symbol.flags & SymbolFlags.Namespace) { + const firstCharCode = name.charCodeAt(0); + if (firstCharCode === CharacterCodes.singleQuote || firstCharCode === CharacterCodes.doubleQuote) { // If the symbol is external module, don't show it in the completion list // (i.e declare module "http" { const x; } | // <= request completion here, "http" should not be there) return undefined; } } - return getCompletionEntryDisplayName(displayName, target, performCharacterChecks); + return getCompletionEntryDisplayName(name, target, performCharacterChecks); } /** @@ -1621,24 +1619,12 @@ namespace ts.Completions { * and checking whether the name is valid identifier name. */ function getCompletionEntryDisplayName(name: string, target: ScriptTarget, performCharacterChecks: boolean): string { - if (!name) { - return undefined; - } - - name = stripQuotes(name); - - if (!name) { - return undefined; - } - // If the user entered name for the symbol was quoted, removing the quotes is not enough, as the name could be an // invalid identifier name. We need to check if whatever was inside the quotes is actually a valid identifier name. // e.g "b a" is valid quoted name but when we strip off the quotes, it is invalid. // We, thus, need to check if whatever was inside the quotes is actually a valid identifier name. - if (performCharacterChecks) { - if (!isIdentifierText(name, target)) { - return undefined; - } + if (performCharacterChecks && !isIdentifierText(name, target)) { + return undefined; } return name; diff --git a/src/services/importTracker.ts b/src/services/importTracker.ts index ecd52baa7dd..f17406b16f1 100644 --- a/src/services/importTracker.ts +++ b/src/services/importTracker.ts @@ -440,7 +440,7 @@ namespace ts.FindAllReferences { function getExport(): ExportedSymbol | ImportedSymbol | undefined { const parent = node.parent!; - if (symbol.flags & SymbolFlags.Export) { + if (symbol.exportSymbol) { if (parent.kind === SyntaxKind.PropertyAccessExpression) { // When accessing an export of a JS module, there's no alias. The symbol will still be flagged as an export even though we're at the use. // So check that we are at the declaration. @@ -449,9 +449,7 @@ namespace ts.FindAllReferences { : undefined; } else { - const { exportSymbol } = symbol; - Debug.assert(!!exportSymbol); - return exportInfo(exportSymbol, getExportKindForDeclaration(parent)); + return exportInfo(symbol.exportSymbol, getExportKindForDeclaration(parent)); } } else { diff --git a/src/services/jsTyping.ts b/src/services/jsTyping.ts index ff90d5bdb34..f31276b6498 100644 --- a/src/services/jsTyping.ts +++ b/src/services/jsTyping.ts @@ -12,7 +12,7 @@ namespace ts.JsTyping { directoryExists: (path: string) => boolean; fileExists: (fileName: string) => boolean; readFile: (path: string, encoding?: string) => string; - readDirectory: (rootDir: string, extensions: string[], excludes: string[], includes: string[], depth?: number) => string[]; + readDirectory: (rootDir: string, extensions: ReadonlyArray, excludes: ReadonlyArray, includes: ReadonlyArray, depth?: number) => string[]; } interface PackageJson { @@ -51,6 +51,7 @@ namespace ts.JsTyping { */ export function discoverTypings( host: TypingResolutionHost, + log: ((message: string) => void) | undefined, fileNames: string[], projectRootPath: Path, safeListPath: Path, @@ -107,8 +108,9 @@ namespace ts.JsTyping { // add typings for unresolved imports if (unresolvedImports) { - for (const moduleId of unresolvedImports) { - const typingName = nodeCoreModules.has(moduleId) ? "node" : moduleId; + const x = unresolvedImports.map(moduleId => nodeCoreModules.has(moduleId) ? "node" : moduleId); + if (x.length && log) log(`Inferred typings from unresolved imports: ${JSON.stringify(x)}`); + for (const typingName of x) { if (!inferredTypings.has(typingName)) { inferredTypings.set(typingName, undefined); } @@ -136,7 +138,9 @@ namespace ts.JsTyping { newTypingNames.push(typing); } }); - return { cachedTypingPaths, newTypingNames, filesToWatch }; + const result = { cachedTypingPaths, newTypingNames, filesToWatch }; + if (log) log(`Result: ${JSON.stringify(result)}`); + return result; function addInferredTyping(typingName: string) { if (!inferredTypings.has(typingName)) { @@ -153,6 +157,7 @@ namespace ts.JsTyping { } filesToWatch.push(jsonPath); + if (log) log(`Searching for typing names in '${jsonPath}' dependencies`); const jsonConfig: PackageJson = readConfigFile(jsonPath, (path: string) => host.readFile(path)).config; addInferredTypingsFromKeys(jsonConfig.dependencies); addInferredTypingsFromKeys(jsonConfig.devDependencies); @@ -175,19 +180,23 @@ namespace ts.JsTyping { * @param fileNames are the names for source files in the project */ function getTypingNamesFromSourceFileNames(fileNames: string[]) { - for (const j of fileNames) { - if (!hasJavaScriptFileExtension(j)) continue; + const fromFileNames = mapDefined(fileNames, j => { + if (!hasJavaScriptFileExtension(j)) return undefined; const inferredTypingName = removeFileExtension(getBaseFileName(j.toLowerCase())); const cleanedTypingName = inferredTypingName.replace(/((?:\.|-)min(?=\.|$))|((?:-|\.)\d+)/g, ""); - const safe = safeList.get(cleanedTypingName); - if (safe !== undefined) { + return safeList.get(cleanedTypingName); + }); + if (fromFileNames.length) { + if (log) log(`Inferred typings from file names: ${JSON.stringify(fromFileNames)}`); + for (const safe of fromFileNames) { addInferredTyping(safe); } } const hasJsxFile = some(fileNames, f => fileExtensionIs(f, Extension.Jsx)); if (hasJsxFile) { + if (log) log(`Inferred 'react' typings due to presence of '.jsx' extension`); addInferredTyping("react"); } } @@ -206,6 +215,7 @@ namespace ts.JsTyping { // depth of 2, so we access `node_modules/foo` but not `node_modules/foo/bar` const fileNames = host.readDirectory(packagesFolderPath, [".json"], /*excludes*/ undefined, /*includes*/ undefined, /*depth*/ 2); + if (log) log(`Searching for typing names in ${packagesFolderPath}; all files: ${JSON.stringify(fileNames)}`); for (const fileName of fileNames) { const normalizedFileName = normalizePath(fileName); const baseFileName = getBaseFileName(normalizedFileName); diff --git a/src/services/pathCompletions.ts b/src/services/pathCompletions.ts index bc94fb7dbc5..9b645280a1e 100644 --- a/src/services/pathCompletions.ts +++ b/src/services/pathCompletions.ts @@ -40,19 +40,14 @@ namespace ts.Completions.PathCompletions { rootDirs = map(rootDirs, rootDirectory => normalizePath(isRootedDiskPath(rootDirectory) ? rootDirectory : combinePaths(basePath, rootDirectory))); // Determine the path to the directory containing the script relative to the root directory it is contained within - let relativeDirectory: string; - for (const rootDirectory of rootDirs) { - if (containsPath(rootDirectory, scriptPath, basePath, ignoreCase)) { - relativeDirectory = scriptPath.substr(rootDirectory.length); - break; - } - } + const relativeDirectory = forEach(rootDirs, rootDirectory => + containsPath(rootDirectory, scriptPath, basePath, ignoreCase) ? scriptPath.substr(rootDirectory.length) : undefined); // Now find a path for each potential directory that is to be merged with the one containing the script return deduplicate(map(rootDirs, rootDirectory => combinePaths(rootDirectory, relativeDirectory))); } - function getCompletionEntriesForDirectoryFragmentWithRootDirs(rootDirs: string[], fragment: string, scriptPath: string, extensions: string[], includeExtensions: boolean, span: TextSpan, compilerOptions: CompilerOptions, host: LanguageServiceHost, exclude?: string): CompletionEntry[] { + function getCompletionEntriesForDirectoryFragmentWithRootDirs(rootDirs: string[], fragment: string, scriptPath: string, extensions: ReadonlyArray, includeExtensions: boolean, span: TextSpan, compilerOptions: CompilerOptions, host: LanguageServiceHost, exclude?: string): CompletionEntry[] { const basePath = compilerOptions.project || host.getCurrentDirectory(); const ignoreCase = !(host.useCaseSensitiveFileNames && host.useCaseSensitiveFileNames()); const baseDirectories = getBaseDirectoriesFromRootDirs(rootDirs, basePath, scriptPath, ignoreCase); @@ -69,7 +64,7 @@ namespace ts.Completions.PathCompletions { /** * Given a path ending at a directory, gets the completions for the path, and filters for those entries containing the basename. */ - function getCompletionEntriesForDirectoryFragment(fragment: string, scriptPath: string, extensions: string[], includeExtensions: boolean, span: TextSpan, host: LanguageServiceHost, exclude?: string, result: CompletionEntry[] = []): CompletionEntry[] { + function getCompletionEntriesForDirectoryFragment(fragment: string, scriptPath: string, extensions: ReadonlyArray, includeExtensions: boolean, span: TextSpan, host: LanguageServiceHost, exclude?: string, result: CompletionEntry[] = []): CompletionEntry[] { if (fragment === undefined) { fragment = ""; } @@ -190,7 +185,7 @@ namespace ts.Completions.PathCompletions { return result; } - function getModulesForPathsPattern(fragment: string, baseUrl: string, pattern: string, fileExtensions: string[], host: LanguageServiceHost): string[] { + function getModulesForPathsPattern(fragment: string, baseUrl: string, pattern: string, fileExtensions: ReadonlyArray, host: LanguageServiceHost): string[] { if (host.readDirectory) { const parsed = hasZeroOrOneAsteriskCharacter(pattern) ? tryParsePattern(pattern) : undefined; if (parsed) { @@ -505,7 +500,7 @@ namespace ts.Completions.PathCompletions { return tryIOAndConsumeErrors(host, host.getDirectories, directoryName); } - function tryReadDirectory(host: LanguageServiceHost, path: string, extensions?: string[], exclude?: string[], include?: string[]): string[] { + function tryReadDirectory(host: LanguageServiceHost, path: string, extensions?: ReadonlyArray, exclude?: ReadonlyArray, include?: ReadonlyArray): string[] { return tryIOAndConsumeErrors(host, host.readDirectory, path, extensions, exclude, include); } diff --git a/src/services/refactorProvider.ts b/src/services/refactorProvider.ts index 3c02ddf671c..432df8c53d0 100644 --- a/src/services/refactorProvider.ts +++ b/src/services/refactorProvider.ts @@ -33,22 +33,9 @@ namespace ts { refactors.set(refactor.name, refactor); } - export function getApplicableRefactors(context: RefactorContext): ApplicableRefactorInfo[] | undefined { - let results: ApplicableRefactorInfo[]; - const refactorList: Refactor[] = []; - refactors.forEach(refactor => { - refactorList.push(refactor); - }); - for (const refactor of refactorList) { - if (context.cancellationToken && context.cancellationToken.isCancellationRequested()) { - return results; - } - const infos = refactor.getAvailableActions(context); - if (infos && infos.length) { - (results || (results = [])).push(...infos); - } - } - return results; + export function getApplicableRefactors(context: RefactorContext): ApplicableRefactorInfo[] { + return flatMapIter(refactors.values(), refactor => + context.cancellationToken && context.cancellationToken.isCancellationRequested() ? [] : refactor.getAvailableActions(context)); } export function getEditsForRefactor(context: RefactorContext, refactorName: string, actionName: string): RefactorEditInfo | undefined { diff --git a/src/services/services.ts b/src/services/services.ts index 28a29352b80..7cb70ed5a07 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -1760,21 +1760,14 @@ namespace ts { function getCodeFixesAtPosition(fileName: string, start: number, end: number, errorCodes: number[], formatOptions: FormatCodeSettings): CodeAction[] { synchronizeHostData(); const sourceFile = getValidSourceFile(fileName); - const span = { start, length: end - start }; + const span = createTextSpanFromBounds(start, end); const newLineCharacter = getNewLineOrDefaultFromHost(host); + const rulesProvider = getRuleProvider(formatOptions); - let allFixes: CodeAction[] = []; - - forEach(deduplicate(errorCodes), errorCode => { + return flatMap(deduplicate(errorCodes), errorCode => { cancellationToken.throwIfCancellationRequested(); - const rulesProvider = getRuleProvider(formatOptions); - const fixes = codefix.getFixes({ errorCode, sourceFile, span, program, newLineCharacter, host, cancellationToken, rulesProvider }); - if (fixes) { - allFixes = allFixes.concat(fixes); - } + return codefix.getFixes({ errorCode, sourceFile, span, program, newLineCharacter, host, cancellationToken, rulesProvider }); }); - - return allFixes; } function getDocCommentTemplateAtPosition(fileName: string, position: number): TextInsertion { @@ -2055,42 +2048,33 @@ namespace ts { } function initializeNameTable(sourceFile: SourceFile): void { - const nameTable = createUnderscoreEscapedMap(); - - walk(sourceFile); - sourceFile.nameTable = nameTable; - - function walk(node: Node) { - switch (node.kind) { - case SyntaxKind.Identifier: - setNameTable((node).text, node); - break; - case SyntaxKind.StringLiteral: - case SyntaxKind.NumericLiteral: - // We want to store any numbers/strings if they were a name that could be - // related to a declaration. So, if we have 'import x = require("something")' - // then we want 'something' to be in the name table. Similarly, if we have - // "a['propname']" then we want to store "propname" in the name table. - if (isDeclarationName(node) || - node.parent.kind === SyntaxKind.ExternalModuleReference || - isArgumentOfElementAccessExpression(node) || - isLiteralComputedPropertyDeclarationName(node)) { - setNameTable(getEscapedTextOfIdentifierOrLiteral((node)), node); - } - break; - default: - forEachChild(node, walk); - if (node.jsDoc) { - for (const jsDoc of node.jsDoc) { - forEachChild(jsDoc, walk); - } - } + const nameTable = sourceFile.nameTable = createUnderscoreEscapedMap(); + sourceFile.forEachChild(function walk(node) { + if ((isIdentifier(node) || isStringOrNumericLiteral(node) && literalIsName(node)) && node.text) { + const text = getEscapedTextOfIdentifierOrLiteral(node); + nameTable.set(text, nameTable.get(text) === undefined ? node.pos : -1); } - } - function setNameTable(text: __String, node: ts.Node): void { - nameTable.set(text, nameTable.get(text) === undefined ? node.pos : -1); - } + forEachChild(node, walk); + if (node.jsDoc) { + for (const jsDoc of node.jsDoc) { + forEachChild(jsDoc, walk); + } + } + }); + } + + /** + * We want to store any numbers/strings if they were a name that could be + * related to a declaration. So, if we have 'import x = require("something")' + * then we want 'something' to be in the name table. Similarly, if we have + * "a['propname']" then we want to store "propname" in the name table. + */ + function literalIsName(node: ts.StringLiteral | ts.NumericLiteral): boolean { + return isDeclarationName(node) || + node.parent.kind === SyntaxKind.ExternalModuleReference || + isArgumentOfElementAccessExpression(node) || + isLiteralComputedPropertyDeclarationName(node); } function isObjectLiteralElement(node: Node): node is ObjectLiteralElement { diff --git a/src/services/shims.ts b/src/services/shims.ts index 0a0b2c82c5b..2ee1853f18f 100644 --- a/src/services/shims.ts +++ b/src/services/shims.ts @@ -444,7 +444,7 @@ namespace ts { return this.shimHost.getDefaultLibFileName(JSON.stringify(options)); } - public readDirectory(path: string, extensions?: string[], exclude?: string[], include?: string[], depth?: number): string[] { + public readDirectory(path: string, extensions?: ReadonlyArray, exclude?: string[], include?: string[], depth?: number): string[] { const pattern = getFileMatcherPatterns(path, exclude, include, this.shimHost.useCaseSensitiveFileNames(), this.shimHost.getCurrentDirectory()); return JSON.parse(this.shimHost.readDirectory( @@ -483,7 +483,7 @@ namespace ts { } } - public readDirectory(rootDir: string, extensions: string[], exclude: string[], include: string[], depth?: number): string[] { + public readDirectory(rootDir: string, extensions: ReadonlyArray, exclude: ReadonlyArray, include: ReadonlyArray, depth?: number): string[] { const pattern = getFileMatcherPatterns(rootDir, exclude, include, this.shimHost.useCaseSensitiveFileNames(), this.shimHost.getCurrentDirectory()); return JSON.parse(this.shimHost.readDirectory( @@ -1116,6 +1116,7 @@ namespace ts { const info = JSON.parse(discoverTypingsJson); return ts.JsTyping.discoverTypings( this.host, + msg => this.logger.log(msg), info.fileNames, toPath(info.projectRootPath, info.projectRootPath, getCanonicalFileName), toPath(info.safeListPath, info.safeListPath, getCanonicalFileName), diff --git a/src/services/symbolDisplay.ts b/src/services/symbolDisplay.ts index b599839cfd3..0cb3916c9c5 100644 --- a/src/services/symbolDisplay.ts +++ b/src/services/symbolDisplay.ts @@ -2,7 +2,7 @@ namespace ts.SymbolDisplay { // TODO(drosen): use contextual SemanticMeaning. export function getSymbolKind(typeChecker: TypeChecker, symbol: Symbol, location: Node): ScriptElementKind { - const { flags } = symbol; + const flags = getCombinedLocalAndExportSymbolFlags(symbol); if (flags & SymbolFlags.Class) { return getDeclarationOfKind(symbol, SyntaxKind.ClassExpression) ? @@ -34,7 +34,7 @@ namespace ts.SymbolDisplay { if (location.kind === SyntaxKind.ThisKeyword && isExpression(location)) { return ScriptElementKind.parameterElement; } - const { flags } = symbol; + const flags = getCombinedLocalAndExportSymbolFlags(symbol); if (flags & SymbolFlags.Variable) { if (isFirstDeclarationOfSymbolParameter(symbol)) { return ScriptElementKind.parameterElement; @@ -96,7 +96,7 @@ namespace ts.SymbolDisplay { const displayParts: SymbolDisplayPart[] = []; let documentation: SymbolDisplayPart[]; let tags: JSDocTagInfo[]; - const symbolFlags = symbol.flags; + const symbolFlags = ts.getCombinedLocalAndExportSymbolFlags(symbol); let symbolKind = getSymbolKindOfConstructorPropertyMethodAccessorFunctionOrVar(typeChecker, symbol, location); let hasAddedSymbolInfo: boolean; const isThisExpression = location.kind === SyntaxKind.ThisKeyword && isExpression(location); @@ -110,7 +110,7 @@ namespace ts.SymbolDisplay { } let signature: Signature; - type = isThisExpression ? typeChecker.getTypeAtLocation(location) : typeChecker.getTypeOfSymbolAtLocation(symbol, location); + type = isThisExpression ? typeChecker.getTypeAtLocation(location) : typeChecker.getTypeOfSymbolAtLocation(symbol.exportSymbol || symbol, location); if (type) { if (location.parent && location.parent.kind === SyntaxKind.PropertyAccessExpression) { const right = (location.parent).name; @@ -198,7 +198,7 @@ namespace ts.SymbolDisplay { hasAddedSymbolInfo = true; } } - else if ((isNameOfFunctionDeclaration(location) && !(symbol.flags & SymbolFlags.Accessor)) || // name of function declaration + else if ((isNameOfFunctionDeclaration(location) && !(symbolFlags & SymbolFlags.Accessor)) || // name of function declaration (location.kind === SyntaxKind.ConstructorKeyword && location.parent.kind === SyntaxKind.Constructor)) { // At constructor keyword of constructor declaration // get the signature from the declaration and write it const functionDeclaration = location.parent; @@ -429,7 +429,7 @@ namespace ts.SymbolDisplay { if (!documentation) { documentation = symbol.getDocumentationComment(); tags = symbol.getJsDocTags(); - if (documentation.length === 0 && symbol.flags & SymbolFlags.Property) { + if (documentation.length === 0 && symbolFlags & SymbolFlags.Property) { // For some special property access expressions like `exports.foo = foo` or `module.exports.foo = foo` // there documentation comments might be attached to the right hand side symbol of their declarations. // The pattern of such special property access is that the parent symbol is the symbol of the file. diff --git a/src/services/types.ts b/src/services/types.ts index d440ecab28e..bfb8a80b769 100644 --- a/src/services/types.ts +++ b/src/services/types.ts @@ -164,7 +164,7 @@ namespace ts { * LS host can optionally implement these methods to support completions for module specifiers. * Without these methods, only completions for ambient modules will be provided. */ - readDirectory?(path: string, extensions?: string[], exclude?: string[], include?: string[], depth?: number): string[]; + readDirectory?(path: string, extensions?: ReadonlyArray, exclude?: ReadonlyArray, include?: ReadonlyArray, depth?: number): string[]; readFile?(path: string, encoding?: string): string; fileExists?(path: string): boolean; diff --git a/src/services/utilities.ts b/src/services/utilities.ts index 5819b5a60ac..7fcfffc3ee2 100644 --- a/src/services/utilities.ts +++ b/src/services/utilities.ts @@ -1240,10 +1240,13 @@ namespace ts { } export function mapToDisplayParts(writeDisplayParts: (writer: DisplayPartsSymbolWriter) => void): SymbolDisplayPart[] { - writeDisplayParts(displayPartWriter); - const result = displayPartWriter.displayParts(); - displayPartWriter.clear(); - return result; + try { + writeDisplayParts(displayPartWriter); + return displayPartWriter.displayParts(); + } + finally { + displayPartWriter.clear(); + } } export function typeToDisplayParts(typechecker: TypeChecker, type: Type, enclosingDeclaration?: Node, flags?: TypeFormatFlags): SymbolDisplayPart[] { diff --git a/tests/baselines/reference/checkJsdocTypeTag1.js b/tests/baselines/reference/checkJsdocTypeTag1.js index e4586643c22..664a02c6047 100644 --- a/tests/baselines/reference/checkJsdocTypeTag1.js +++ b/tests/baselines/reference/checkJsdocTypeTag1.js @@ -28,7 +28,17 @@ x1(0); /** @type {function (number): number} */ const x2 = (a) => a + 1; -x2(0); +x2(0); + +/** + * @type {object} + */ +var props = {}; + +/** + * @type {Object} + */ +var props = {}; //// [0.js] // @ts-check @@ -54,3 +64,11 @@ x1(0); /** @type {function (number): number} */ var x2 = function (a) { return a + 1; }; x2(0); +/** + * @type {object} + */ +var props = {}; +/** + * @type {Object} + */ +var props = {}; diff --git a/tests/baselines/reference/checkJsdocTypeTag1.symbols b/tests/baselines/reference/checkJsdocTypeTag1.symbols index fff51499bc4..cd337f8b556 100644 --- a/tests/baselines/reference/checkJsdocTypeTag1.symbols +++ b/tests/baselines/reference/checkJsdocTypeTag1.symbols @@ -58,3 +58,15 @@ const x2 = (a) => a + 1; x2(0); >x2 : Symbol(x2, Decl(0.js, 28, 5)) +/** + * @type {object} + */ +var props = {}; +>props : Symbol(props, Decl(0.js, 34, 3), Decl(0.js, 39, 3)) + +/** + * @type {Object} + */ +var props = {}; +>props : Symbol(props, Decl(0.js, 34, 3), Decl(0.js, 39, 3)) + diff --git a/tests/baselines/reference/checkJsdocTypeTag1.types b/tests/baselines/reference/checkJsdocTypeTag1.types index 898cf58806a..8e59a2cdb7c 100644 --- a/tests/baselines/reference/checkJsdocTypeTag1.types +++ b/tests/baselines/reference/checkJsdocTypeTag1.types @@ -86,3 +86,17 @@ x2(0); >x2 : (arg0: number) => number >0 : 0 +/** + * @type {object} + */ +var props = {}; +>props : any +>{} : {} + +/** + * @type {Object} + */ +var props = {}; +>props : any +>{} : {} + diff --git a/tests/baselines/reference/importCallExpressionShouldNotGetParen.js b/tests/baselines/reference/importCallExpressionShouldNotGetParen.js new file mode 100644 index 00000000000..07539664aa3 --- /dev/null +++ b/tests/baselines/reference/importCallExpressionShouldNotGetParen.js @@ -0,0 +1,18 @@ +//// [importCallExpressionShouldNotGetParen.ts] +const localeName = "zh-CN"; +import(`./locales/${localeName}.js`).then(bar => { + let x = bar; +}); + +import("./locales/" + localeName + ".js").then(bar => { + let x = bar; +}); + +//// [importCallExpressionShouldNotGetParen.js] +const localeName = "zh-CN"; +import(`./locales/${localeName}.js`).then(bar => { + let x = bar; +}); +import("./locales/" + localeName + ".js").then(bar => { + let x = bar; +}); diff --git a/tests/baselines/reference/importCallExpressionShouldNotGetParen.symbols b/tests/baselines/reference/importCallExpressionShouldNotGetParen.symbols new file mode 100644 index 00000000000..ace34efcf0d --- /dev/null +++ b/tests/baselines/reference/importCallExpressionShouldNotGetParen.symbols @@ -0,0 +1,27 @@ +=== tests/cases/conformance/dynamicImport/importCallExpressionShouldNotGetParen.ts === +const localeName = "zh-CN"; +>localeName : Symbol(localeName, Decl(importCallExpressionShouldNotGetParen.ts, 0, 5)) + +import(`./locales/${localeName}.js`).then(bar => { +>import(`./locales/${localeName}.js`).then : Symbol(Promise.then, Decl(lib.es5.d.ts, --, --)) +>localeName : Symbol(localeName, Decl(importCallExpressionShouldNotGetParen.ts, 0, 5)) +>then : Symbol(Promise.then, Decl(lib.es5.d.ts, --, --)) +>bar : Symbol(bar, Decl(importCallExpressionShouldNotGetParen.ts, 1, 42)) + + let x = bar; +>x : Symbol(x, Decl(importCallExpressionShouldNotGetParen.ts, 2, 7)) +>bar : Symbol(bar, Decl(importCallExpressionShouldNotGetParen.ts, 1, 42)) + +}); + +import("./locales/" + localeName + ".js").then(bar => { +>import("./locales/" + localeName + ".js").then : Symbol(Promise.then, Decl(lib.es5.d.ts, --, --)) +>localeName : Symbol(localeName, Decl(importCallExpressionShouldNotGetParen.ts, 0, 5)) +>then : Symbol(Promise.then, Decl(lib.es5.d.ts, --, --)) +>bar : Symbol(bar, Decl(importCallExpressionShouldNotGetParen.ts, 5, 47)) + + let x = bar; +>x : Symbol(x, Decl(importCallExpressionShouldNotGetParen.ts, 6, 7)) +>bar : Symbol(bar, Decl(importCallExpressionShouldNotGetParen.ts, 5, 47)) + +}); diff --git a/tests/baselines/reference/importCallExpressionShouldNotGetParen.types b/tests/baselines/reference/importCallExpressionShouldNotGetParen.types new file mode 100644 index 00000000000..3e4001dc5b7 --- /dev/null +++ b/tests/baselines/reference/importCallExpressionShouldNotGetParen.types @@ -0,0 +1,39 @@ +=== tests/cases/conformance/dynamicImport/importCallExpressionShouldNotGetParen.ts === +const localeName = "zh-CN"; +>localeName : "zh-CN" +>"zh-CN" : "zh-CN" + +import(`./locales/${localeName}.js`).then(bar => { +>import(`./locales/${localeName}.js`).then(bar => { let x = bar;}) : Promise +>import(`./locales/${localeName}.js`).then : (onfulfilled?: (value: any) => TResult1 | PromiseLike, onrejected?: (reason: any) => TResult2 | PromiseLike) => Promise +>import(`./locales/${localeName}.js`) : Promise +>`./locales/${localeName}.js` : string +>localeName : "zh-CN" +>then : (onfulfilled?: (value: any) => TResult1 | PromiseLike, onrejected?: (reason: any) => TResult2 | PromiseLike) => Promise +>bar => { let x = bar;} : (bar: any) => void +>bar : any + + let x = bar; +>x : any +>bar : any + +}); + +import("./locales/" + localeName + ".js").then(bar => { +>import("./locales/" + localeName + ".js").then(bar => { let x = bar;}) : Promise +>import("./locales/" + localeName + ".js").then : (onfulfilled?: (value: any) => TResult1 | PromiseLike, onrejected?: (reason: any) => TResult2 | PromiseLike) => Promise +>import("./locales/" + localeName + ".js") : Promise +>"./locales/" + localeName + ".js" : string +>"./locales/" + localeName : string +>"./locales/" : "./locales/" +>localeName : "zh-CN" +>".js" : ".js" +>then : (onfulfilled?: (value: any) => TResult1 | PromiseLike, onrejected?: (reason: any) => TResult2 | PromiseLike) => Promise +>bar => { let x = bar;} : (bar: any) => void +>bar : any + + let x = bar; +>x : any +>bar : any + +}); diff --git a/tests/baselines/reference/importCallExpressionWithTypeArgument.js b/tests/baselines/reference/importCallExpressionWithTypeArgument.js index d8690cf5d75..cdea13b2fac 100644 --- a/tests/baselines/reference/importCallExpressionWithTypeArgument.js +++ b/tests/baselines/reference/importCallExpressionWithTypeArgument.js @@ -18,5 +18,5 @@ function foo() { return "foo"; } exports.foo = foo; //// [1.js] "use strict"; -var p1 = (import)("./0"); // error -var p2 = (import)("./0"); // error +var p1 = Promise.resolve().then(function () { return require("./0"); }); // error +var p2 = Promise.resolve().then(function () { return require("./0"); }); // error diff --git a/tests/baselines/reference/jsdocTypeTag.js b/tests/baselines/reference/jsdocTypeTag.js index ff92f0f0474..4789b1d26af 100644 --- a/tests/baselines/reference/jsdocTypeTag.js +++ b/tests/baselines/reference/jsdocTypeTag.js @@ -55,6 +55,9 @@ var nullable; /** @type {Object} */ var Obj; +/** @type {object} */ +var obj; + /** @type {Function} */ var Func; @@ -77,6 +80,7 @@ var P: Promise; var p: Promise; var nullable: number | null; var Obj: any; +var obj: any; var Func: Function; @@ -117,6 +121,8 @@ var p; var nullable; /** @type {Object} */ var Obj; +/** @type {object} */ +var obj; /** @type {Function} */ var Func; //// [b.js] @@ -138,4 +144,5 @@ var P; var p; var nullable; var Obj; +var obj; var Func; diff --git a/tests/baselines/reference/jsdocTypeTag.symbols b/tests/baselines/reference/jsdocTypeTag.symbols index 5931c1cfe53..77df5b119f4 100644 --- a/tests/baselines/reference/jsdocTypeTag.symbols +++ b/tests/baselines/reference/jsdocTypeTag.symbols @@ -71,9 +71,13 @@ var nullable; var Obj; >Obj : Symbol(Obj, Decl(a.js, 52, 3), Decl(b.ts, 17, 3)) +/** @type {object} */ +var obj; +>obj : Symbol(obj, Decl(a.js, 55, 3), Decl(b.ts, 18, 3)) + /** @type {Function} */ var Func; ->Func : Symbol(Func, Decl(a.js, 55, 3), Decl(b.ts, 18, 3)) +>Func : Symbol(Func, Decl(a.js, 58, 3), Decl(b.ts, 19, 3)) === tests/cases/conformance/jsdoc/b.ts === var S: string; @@ -132,7 +136,10 @@ var nullable: number | null; var Obj: any; >Obj : Symbol(Obj, Decl(a.js, 52, 3), Decl(b.ts, 17, 3)) +var obj: any; +>obj : Symbol(obj, Decl(a.js, 55, 3), Decl(b.ts, 18, 3)) + var Func: Function; ->Func : Symbol(Func, Decl(a.js, 55, 3), Decl(b.ts, 18, 3)) +>Func : Symbol(Func, Decl(a.js, 58, 3), Decl(b.ts, 19, 3)) >Function : Symbol(Function, Decl(lib.d.ts, --, --), Decl(lib.d.ts, --, --)) diff --git a/tests/baselines/reference/jsdocTypeTag.types b/tests/baselines/reference/jsdocTypeTag.types index f3e7dfad634..9813a186a00 100644 --- a/tests/baselines/reference/jsdocTypeTag.types +++ b/tests/baselines/reference/jsdocTypeTag.types @@ -71,6 +71,10 @@ var nullable; var Obj; >Obj : any +/** @type {object} */ +var obj; +>obj : any + /** @type {Function} */ var Func; >Func : Function @@ -135,6 +139,9 @@ var nullable: number | null; var Obj: any; >Obj : any +var obj: any; +>obj : any + var Func: Function; >Func : Function >Function : Function diff --git a/tests/baselines/reference/mergedDeclarationExports.errors.txt b/tests/baselines/reference/mergedDeclarationExports.errors.txt new file mode 100644 index 00000000000..f7a09ad4bc6 --- /dev/null +++ b/tests/baselines/reference/mergedDeclarationExports.errors.txt @@ -0,0 +1,44 @@ +tests/cases/compiler/mergedDeclarationExports.ts(13,11): error TS2395: Individual declarations in merged declaration 'c' must be all exported or all local. +tests/cases/compiler/mergedDeclarationExports.ts(14,18): error TS2395: Individual declarations in merged declaration 'c' must be all exported or all local. +tests/cases/compiler/mergedDeclarationExports.ts(17,11): error TS2395: Individual declarations in merged declaration 'd' must be all exported or all local. +tests/cases/compiler/mergedDeclarationExports.ts(18,14): error TS2395: Individual declarations in merged declaration 'd' must be all exported or all local. +tests/cases/compiler/mergedDeclarationExports.ts(21,11): error TS2395: Individual declarations in merged declaration 'N' must be all exported or all local. +tests/cases/compiler/mergedDeclarationExports.ts(22,18): error TS2395: Individual declarations in merged declaration 'N' must be all exported or all local. + + +==== tests/cases/compiler/mergedDeclarationExports.ts (6 errors) ==== + // OK -- one is type, one is value + interface b {} + export const b = 1; + + // OK -- one is a type, one is a namespace, one is a value. + type t = 0; + namespace t { interface I {} } + export const t = 0; + + // Should get errors if they have some meaning in common. + + // both types + interface c {} + ~ +!!! error TS2395: Individual declarations in merged declaration 'c' must be all exported or all local. + export interface c {} + ~ +!!! error TS2395: Individual declarations in merged declaration 'c' must be all exported or all local. + + // both types (class is also value, but that doesn't matter) + interface d {} + ~ +!!! error TS2395: Individual declarations in merged declaration 'd' must be all exported or all local. + export class d {} + ~ +!!! error TS2395: Individual declarations in merged declaration 'd' must be all exported or all local. + + // both namespaces + namespace N { } + ~ +!!! error TS2395: Individual declarations in merged declaration 'N' must be all exported or all local. + export namespace N {} + ~ +!!! error TS2395: Individual declarations in merged declaration 'N' must be all exported or all local. + \ No newline at end of file diff --git a/tests/baselines/reference/mergedDeclarationExports.js b/tests/baselines/reference/mergedDeclarationExports.js new file mode 100644 index 00000000000..068f7b683c3 --- /dev/null +++ b/tests/baselines/reference/mergedDeclarationExports.js @@ -0,0 +1,36 @@ +//// [mergedDeclarationExports.ts] +// OK -- one is type, one is value +interface b {} +export const b = 1; + +// OK -- one is a type, one is a namespace, one is a value. +type t = 0; +namespace t { interface I {} } +export const t = 0; + +// Should get errors if they have some meaning in common. + +// both types +interface c {} +export interface c {} + +// both types (class is also value, but that doesn't matter) +interface d {} +export class d {} + +// both namespaces +namespace N { } +export namespace N {} + + +//// [mergedDeclarationExports.js] +"use strict"; +exports.__esModule = true; +exports.b = 1; +exports.t = 0; +var d = (function () { + function d() { + } + return d; +}()); +exports.d = d; diff --git a/tests/baselines/reference/parseErrorIncorrectReturnToken.errors.txt b/tests/baselines/reference/parseErrorIncorrectReturnToken.errors.txt new file mode 100644 index 00000000000..2cf728848e4 --- /dev/null +++ b/tests/baselines/reference/parseErrorIncorrectReturnToken.errors.txt @@ -0,0 +1,37 @@ +tests/cases/compiler/parseErrorIncorrectReturnToken.ts(2,17): error TS1005: ':' expected. +tests/cases/compiler/parseErrorIncorrectReturnToken.ts(4,22): error TS1005: '=>' expected. +tests/cases/compiler/parseErrorIncorrectReturnToken.ts(4,24): error TS2693: 'string' only refers to a type, but is being used as a value here. +tests/cases/compiler/parseErrorIncorrectReturnToken.ts(9,18): error TS1005: '{' expected. +tests/cases/compiler/parseErrorIncorrectReturnToken.ts(9,21): error TS2693: 'string' only refers to a type, but is being used as a value here. +tests/cases/compiler/parseErrorIncorrectReturnToken.ts(9,28): error TS1005: ';' expected. +tests/cases/compiler/parseErrorIncorrectReturnToken.ts(12,1): error TS1128: Declaration or statement expected. + + +==== tests/cases/compiler/parseErrorIncorrectReturnToken.ts (7 errors) ==== + type F1 = { + (n: number) => string; // should be : not => + ~~ +!!! error TS1005: ':' expected. + } + type F2 = (n: number): string; // should be => not : + ~ +!!! error TS1005: '=>' expected. + ~~~~~~ +!!! error TS2693: 'string' only refers to a type, but is being used as a value here. + + // doesn't work in non-type contexts, where the return type is optional + let f = (n: number) => string => n.toString(); + let o = { + m(n: number) => string { + ~~ +!!! error TS1005: '{' expected. + ~~~~~~ +!!! error TS2693: 'string' only refers to a type, but is being used as a value here. + ~ +!!! error TS1005: ';' expected. + return n.toString(); + } + }; + ~ +!!! error TS1128: Declaration or statement expected. + \ No newline at end of file diff --git a/tests/baselines/reference/parseErrorIncorrectReturnToken.js b/tests/baselines/reference/parseErrorIncorrectReturnToken.js new file mode 100644 index 00000000000..3d2a1d17a4a --- /dev/null +++ b/tests/baselines/reference/parseErrorIncorrectReturnToken.js @@ -0,0 +1,27 @@ +//// [parseErrorIncorrectReturnToken.ts] +type F1 = { + (n: number) => string; // should be : not => +} +type F2 = (n: number): string; // should be => not : + +// doesn't work in non-type contexts, where the return type is optional +let f = (n: number) => string => n.toString(); +let o = { + m(n: number) => string { + return n.toString(); + } +}; + + +//// [parseErrorIncorrectReturnToken.js] +string; // should be => not : +// doesn't work in non-type contexts, where the return type is optional +var f = function (n) { return function (string) { return n.toString(); }; }; +var o = { + m: function (n) { } +}; +string; +{ + return n.toString(); +} +; diff --git a/tests/baselines/reference/tsConfig/Default initialized TSConfig/tsconfig.json b/tests/baselines/reference/tsConfig/Default initialized TSConfig/tsconfig.json index 772c218eb4e..b79b4f0f181 100644 --- a/tests/baselines/reference/tsConfig/Default initialized TSConfig/tsconfig.json +++ b/tests/baselines/reference/tsConfig/Default initialized TSConfig/tsconfig.json @@ -2,7 +2,7 @@ "compilerOptions": { /* Basic Options */ "target": "es5", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', or 'ESNEXT'. */ - "module": "commonjs", /* Specify module code generation: 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */ + "module": "commonjs", /* Specify module code generation: 'none', commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */ // "lib": [], /* Specify library files to be included in the compilation: */ // "allowJs": true, /* Allow javascript files to be compiled. */ // "checkJs": true, /* Report errors in .js files. */ diff --git a/tests/baselines/reference/tsConfig/Initialized TSConfig with boolean value compiler options/tsconfig.json b/tests/baselines/reference/tsConfig/Initialized TSConfig with boolean value compiler options/tsconfig.json index 7a9b895636c..ecbaaf9d961 100644 --- a/tests/baselines/reference/tsConfig/Initialized TSConfig with boolean value compiler options/tsconfig.json +++ b/tests/baselines/reference/tsConfig/Initialized TSConfig with boolean value compiler options/tsconfig.json @@ -2,7 +2,7 @@ "compilerOptions": { /* Basic Options */ "target": "es5", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', or 'ESNEXT'. */ - "module": "commonjs", /* Specify module code generation: 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */ + "module": "commonjs", /* Specify module code generation: 'none', commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */ // "lib": [], /* Specify library files to be included in the compilation: */ // "allowJs": true, /* Allow javascript files to be compiled. */ // "checkJs": true, /* Report errors in .js files. */ diff --git a/tests/baselines/reference/tsConfig/Initialized TSConfig with enum value compiler options/tsconfig.json b/tests/baselines/reference/tsConfig/Initialized TSConfig with enum value compiler options/tsconfig.json index 70d5ed9d738..321547908d9 100644 --- a/tests/baselines/reference/tsConfig/Initialized TSConfig with enum value compiler options/tsconfig.json +++ b/tests/baselines/reference/tsConfig/Initialized TSConfig with enum value compiler options/tsconfig.json @@ -2,7 +2,7 @@ "compilerOptions": { /* Basic Options */ "target": "es5", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', or 'ESNEXT'. */ - "module": "commonjs", /* Specify module code generation: 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */ + "module": "commonjs", /* Specify module code generation: 'none', commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */ // "lib": [], /* Specify library files to be included in the compilation: */ // "allowJs": true, /* Allow javascript files to be compiled. */ // "checkJs": true, /* Report errors in .js files. */ diff --git a/tests/baselines/reference/tsConfig/Initialized TSConfig with files options/tsconfig.json b/tests/baselines/reference/tsConfig/Initialized TSConfig with files options/tsconfig.json index 914b9d99d1b..ec0ac6e4c32 100644 --- a/tests/baselines/reference/tsConfig/Initialized TSConfig with files options/tsconfig.json +++ b/tests/baselines/reference/tsConfig/Initialized TSConfig with files options/tsconfig.json @@ -2,7 +2,7 @@ "compilerOptions": { /* Basic Options */ "target": "es5", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', or 'ESNEXT'. */ - "module": "commonjs", /* Specify module code generation: 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */ + "module": "commonjs", /* Specify module code generation: 'none', commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */ // "lib": [], /* Specify library files to be included in the compilation: */ // "allowJs": true, /* Allow javascript files to be compiled. */ // "checkJs": true, /* Report errors in .js files. */ diff --git a/tests/baselines/reference/tsConfig/Initialized TSConfig with incorrect compiler option value/tsconfig.json b/tests/baselines/reference/tsConfig/Initialized TSConfig with incorrect compiler option value/tsconfig.json index 50bd28442bb..655ece4d6d0 100644 --- a/tests/baselines/reference/tsConfig/Initialized TSConfig with incorrect compiler option value/tsconfig.json +++ b/tests/baselines/reference/tsConfig/Initialized TSConfig with incorrect compiler option value/tsconfig.json @@ -2,7 +2,7 @@ "compilerOptions": { /* Basic Options */ "target": "es5", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', or 'ESNEXT'. */ - "module": "commonjs", /* Specify module code generation: 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */ + "module": "commonjs", /* Specify module code generation: 'none', commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */ "lib": ["es5","es2015.promise"], /* Specify library files to be included in the compilation: */ // "allowJs": true, /* Allow javascript files to be compiled. */ // "checkJs": true, /* Report errors in .js files. */ diff --git a/tests/baselines/reference/tsConfig/Initialized TSConfig with incorrect compiler option/tsconfig.json b/tests/baselines/reference/tsConfig/Initialized TSConfig with incorrect compiler option/tsconfig.json index 772c218eb4e..b79b4f0f181 100644 --- a/tests/baselines/reference/tsConfig/Initialized TSConfig with incorrect compiler option/tsconfig.json +++ b/tests/baselines/reference/tsConfig/Initialized TSConfig with incorrect compiler option/tsconfig.json @@ -2,7 +2,7 @@ "compilerOptions": { /* Basic Options */ "target": "es5", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', or 'ESNEXT'. */ - "module": "commonjs", /* Specify module code generation: 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */ + "module": "commonjs", /* Specify module code generation: 'none', commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */ // "lib": [], /* Specify library files to be included in the compilation: */ // "allowJs": true, /* Allow javascript files to be compiled. */ // "checkJs": true, /* Report errors in .js files. */ diff --git a/tests/baselines/reference/tsConfig/Initialized TSConfig with list compiler options with enum value/tsconfig.json b/tests/baselines/reference/tsConfig/Initialized TSConfig with list compiler options with enum value/tsconfig.json index afae7193d58..81b1636b8cb 100644 --- a/tests/baselines/reference/tsConfig/Initialized TSConfig with list compiler options with enum value/tsconfig.json +++ b/tests/baselines/reference/tsConfig/Initialized TSConfig with list compiler options with enum value/tsconfig.json @@ -2,7 +2,7 @@ "compilerOptions": { /* Basic Options */ "target": "es5", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', or 'ESNEXT'. */ - "module": "commonjs", /* Specify module code generation: 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */ + "module": "commonjs", /* Specify module code generation: 'none', commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */ "lib": ["es5","es2015.core"], /* Specify library files to be included in the compilation: */ // "allowJs": true, /* Allow javascript files to be compiled. */ // "checkJs": true, /* Report errors in .js files. */ diff --git a/tests/baselines/reference/tsConfig/Initialized TSConfig with list compiler options/tsconfig.json b/tests/baselines/reference/tsConfig/Initialized TSConfig with list compiler options/tsconfig.json index a87fe9c5206..66acf6df045 100644 --- a/tests/baselines/reference/tsConfig/Initialized TSConfig with list compiler options/tsconfig.json +++ b/tests/baselines/reference/tsConfig/Initialized TSConfig with list compiler options/tsconfig.json @@ -2,7 +2,7 @@ "compilerOptions": { /* Basic Options */ "target": "es5", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', or 'ESNEXT'. */ - "module": "commonjs", /* Specify module code generation: 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */ + "module": "commonjs", /* Specify module code generation: 'none', commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */ // "lib": [], /* Specify library files to be included in the compilation: */ // "allowJs": true, /* Allow javascript files to be compiled. */ // "checkJs": true, /* Report errors in .js files. */ diff --git a/tests/cases/compiler/mergedDeclarationExports.ts b/tests/cases/compiler/mergedDeclarationExports.ts new file mode 100644 index 00000000000..a27f1186e9f --- /dev/null +++ b/tests/cases/compiler/mergedDeclarationExports.ts @@ -0,0 +1,22 @@ +// OK -- one is type, one is value +interface b {} +export const b = 1; + +// OK -- one is a type, one is a namespace, one is a value. +type t = 0; +namespace t { interface I {} } +export const t = 0; + +// Should get errors if they have some meaning in common. + +// both types +interface c {} +export interface c {} + +// both types (class is also value, but that doesn't matter) +interface d {} +export class d {} + +// both namespaces +namespace N { } +export namespace N {} diff --git a/tests/cases/compiler/parseErrorIncorrectReturnToken.ts b/tests/cases/compiler/parseErrorIncorrectReturnToken.ts new file mode 100644 index 00000000000..0f45c38a3b1 --- /dev/null +++ b/tests/cases/compiler/parseErrorIncorrectReturnToken.ts @@ -0,0 +1,13 @@ + +type F1 = { + (n: number) => string; // should be : not => +} +type F2 = (n: number): string; // should be => not : + +// doesn't work in non-type contexts, where the return type is optional +let f = (n: number) => string => n.toString(); +let o = { + m(n: number) => string { + return n.toString(); + } +}; diff --git a/tests/cases/conformance/dynamicImport/importCallExpressionShouldNotGetParen.ts b/tests/cases/conformance/dynamicImport/importCallExpressionShouldNotGetParen.ts new file mode 100644 index 00000000000..6734e888a5e --- /dev/null +++ b/tests/cases/conformance/dynamicImport/importCallExpressionShouldNotGetParen.ts @@ -0,0 +1,11 @@ +// @module: esnext +// @target: es6 +// @noImplicitAny: true +const localeName = "zh-CN"; +import(`./locales/${localeName}.js`).then(bar => { + let x = bar; +}); + +import("./locales/" + localeName + ".js").then(bar => { + let x = bar; +}); \ No newline at end of file diff --git a/tests/cases/conformance/jsdoc/checkJsdocTypeTag1.ts b/tests/cases/conformance/jsdoc/checkJsdocTypeTag1.ts index fbed9d83b22..4dc7b934571 100644 --- a/tests/cases/conformance/jsdoc/checkJsdocTypeTag1.ts +++ b/tests/cases/conformance/jsdoc/checkJsdocTypeTag1.ts @@ -31,4 +31,14 @@ x1(0); /** @type {function (number): number} */ const x2 = (a) => a + 1; -x2(0); \ No newline at end of file +x2(0); + +/** + * @type {object} + */ +var props = {}; + +/** + * @type {Object} + */ +var props = {}; \ No newline at end of file diff --git a/tests/cases/conformance/jsdoc/jsdocTypeTag.ts b/tests/cases/conformance/jsdoc/jsdocTypeTag.ts index d566c61e185..1c19771a3cd 100644 --- a/tests/cases/conformance/jsdoc/jsdocTypeTag.ts +++ b/tests/cases/conformance/jsdoc/jsdocTypeTag.ts @@ -57,6 +57,9 @@ var nullable; /** @type {Object} */ var Obj; +/** @type {object} */ +var obj; + /** @type {Function} */ var Func; @@ -79,4 +82,5 @@ var P: Promise; var p: Promise; var nullable: number | null; var Obj: any; +var obj: any; var Func: Function; diff --git a/tests/cases/fourslash/commentsExternalModules.ts b/tests/cases/fourslash/commentsExternalModules.ts index 4f1a82bc0ce..582c6a31357 100644 --- a/tests/cases/fourslash/commentsExternalModules.ts +++ b/tests/cases/fourslash/commentsExternalModules.ts @@ -35,7 +35,7 @@ goTo.file("commentsExternalModules_file0.ts"); verify.quickInfoAt("1", "namespace m1", "Namespace comment"); goTo.marker('2'); -verify.completionListContains("b", "var m1.b: number", "b's comment"); +verify.completionListContains("b", "var b: number", "b's comment"); verify.completionListContains("foo", "function foo(): number", "foo's comment"); goTo.marker('3'); diff --git a/tests/cases/fourslash/commentsModules.ts b/tests/cases/fourslash/commentsModules.ts index c0b8ccc5731..0205b144f16 100644 --- a/tests/cases/fourslash/commentsModules.ts +++ b/tests/cases/fourslash/commentsModules.ts @@ -99,7 +99,7 @@ verify.quickInfoAt("1", "namespace m1", "Namespace comment"); goTo.marker('2'); -verify.completionListContains("b", "var m1.b: number", "b's comment"); +verify.completionListContains("b", "var b: number", "b's comment"); verify.completionListContains("foo", "function foo(): number", "foo's comment"); goTo.marker('3'); diff --git a/tests/cases/fourslash/completionListForUnicodeEscapeName.ts b/tests/cases/fourslash/completionListForUnicodeEscapeName.ts index afafd18a58b..9ada3e35961 100644 --- a/tests/cases/fourslash/completionListForUnicodeEscapeName.ts +++ b/tests/cases/fourslash/completionListForUnicodeEscapeName.ts @@ -6,8 +6,8 @@ /////*3*/ goTo.marker("0"); -verify.not.completionListContains("B"); -verify.not.completionListContains("\u0042"); +verify.completionListContains("B"); +verify.completionListContains("\u0042"); goTo.marker("2"); verify.not.completionListContains("C"); @@ -18,10 +18,9 @@ verify.not.completionListContains("A"); verify.not.completionListContains("\u0041"); goTo.marker("3"); -verify.not.completionListContains("B"); -verify.not.completionListContains("\u0042"); -verify.not.completionListContains("A"); -verify.not.completionListContains("\u0041"); -verify.not.completionListContains("C"); -verify.not.completionListContains("\u0043"); - +verify.completionListContains("B"); +verify.completionListContains("\u0042"); +verify.completionListContains("A"); +verify.completionListContains("\u0041"); +verify.completionListContains("C"); +verify.completionListContains("\u0043"); diff --git a/tests/cases/fourslash/completionListInScope_doesNotIncludeAugmentations.ts b/tests/cases/fourslash/completionListInScope_doesNotIncludeAugmentations.ts new file mode 100644 index 00000000000..de70890c4c6 --- /dev/null +++ b/tests/cases/fourslash/completionListInScope_doesNotIncludeAugmentations.ts @@ -0,0 +1,13 @@ +/// + +// @Filename: /a.ts +////import * as self from "./a"; +//// +////declare module "a" { +//// export const a: number; +////} +//// +/////**/ + +goTo.marker(); +verify.not.completionListContains("a"); diff --git a/tests/cases/fourslash/completionListInvalidMemberNames.ts b/tests/cases/fourslash/completionListInvalidMemberNames.ts index 95abe70415c..e0a65bfca4d 100644 --- a/tests/cases/fourslash/completionListInvalidMemberNames.ts +++ b/tests/cases/fourslash/completionListInvalidMemberNames.ts @@ -19,6 +19,7 @@ verify.completionListContains("bar"); verify.completionListContains("break"); verify.completionListContains("any"); verify.completionListContains("$"); +verify.completionListContains("b"); // Nothing else should show up -verify.completionListCount(4); +verify.completionListCount(5); diff --git a/tests/cases/fourslash/completionListOnAliases2.ts b/tests/cases/fourslash/completionListOnAliases2.ts index 63f895f276e..5933fbe89d4 100644 --- a/tests/cases/fourslash/completionListOnAliases2.ts +++ b/tests/cases/fourslash/completionListOnAliases2.ts @@ -41,19 +41,16 @@ function getVerify(isTypeLocation?: boolean) { verifyValueOrType: verify }; } -function typeLocationVerify(valueMarker: string, verify: (typeMarker: string) => void) { - verify(valueMarker + "Type"); - return valueMarker; -} function verifyModuleM(marker: string) { - const isTypeLocation = marker.indexOf("Type") !== -1; - const { verifyValue, verifyType, verifyValueOrType } = getVerify(isTypeLocation); - if (!isTypeLocation) { - marker = typeLocationVerify(marker, verifyModuleM); - } + verifyModuleMWorker(marker, /*isTypeLocation*/ false); + verifyModuleMWorker(`${marker}Type`, /*isTypeLocation*/ true); +} + +function verifyModuleMWorker(marker: string, isTypeLocation: boolean): void { goTo.marker(marker); + const { verifyValue, verifyType, verifyValueOrType } = getVerify(isTypeLocation); verifyType.completionListContains("I"); verifyValueOrType.completionListContains("C"); verifyValueOrType.completionListContains("E"); @@ -63,8 +60,9 @@ function verifyModuleM(marker: string) { verifyValueOrType.completionListContains("A"); } - // Module m +goTo.marker("1"); +verify.completionListContains("A"); verifyModuleM("1"); // Class C diff --git a/tests/cases/fourslash/completionListWithModulesInsideModuleScope.ts b/tests/cases/fourslash/completionListWithModulesInsideModuleScope.ts index 5d950a5d620..cb6bf283b69 100644 --- a/tests/cases/fourslash/completionListWithModulesInsideModuleScope.ts +++ b/tests/cases/fourslash/completionListWithModulesInsideModuleScope.ts @@ -232,6 +232,7 @@ interface GotoMarkVerifyOptions { isClassScope?: boolean; isTypeLocation?: boolean; + insideMod1?: boolean; } function getVerify(isTypeLocation: boolean) { @@ -243,30 +244,30 @@ function getVerify(isTypeLocation: boolean) { }; } -function goToMarkAndGeneralVerify(marker: string, { isClassScope, isTypeLocation }: GotoMarkVerifyOptions = {}) -{ +function goToMarkAndGeneralVerify(marker: string, { isClassScope, isTypeLocation, insideMod1 }: GotoMarkVerifyOptions = {}) { goTo.marker(marker); + const mod1Dot = insideMod1 ? "" : "mod1."; const verifyValueInModule = isClassScope || isTypeLocation ? verify.not : verify; const verifyValueOrTypeInModule = isClassScope ? verify.not : verify; const verifyTypeInModule = isTypeLocation ? verify : verify.not; verifyValueInModule.completionListContains('mod1var', 'var mod1var: number'); verifyValueInModule.completionListContains('mod1fn', 'function mod1fn(): void'); - verifyValueInModule.completionListContains('mod1evar', 'var mod1.mod1evar: number'); - verifyValueInModule.completionListContains('mod1efn', 'function mod1.mod1efn(): void'); - verifyValueInModule.completionListContains('mod1eexvar', 'var mod1.mod1eexvar: number'); + verifyValueInModule.completionListContains('mod1evar', `var ${mod1Dot}mod1evar: number`); + verifyValueInModule.completionListContains('mod1efn', `function ${mod1Dot}mod1efn(): void`); + verifyValueInModule.completionListContains('mod1eexvar', `var mod1.mod1eexvar: number`); verifyValueInModule.completionListContains('mod3', 'namespace mod3'); verifyValueInModule.completionListContains('shwvar', 'var shwvar: number'); verifyValueInModule.completionListContains('shwfn', 'function shwfn(): void'); verifyTypeInModule.completionListContains('mod1int', 'interface mod1int'); - verifyTypeInModule.completionListContains('mod1eint', 'interface mod1.mod1eint'); + verifyTypeInModule.completionListContains('mod1eint', `interface ${mod1Dot}mod1eint`); verifyTypeInModule.completionListContains('shwint', 'interface shwint'); verifyValueOrTypeInModule.completionListContains('mod1cls', 'class mod1cls'); verifyValueOrTypeInModule.completionListContains('mod1mod', 'namespace mod1mod'); - verifyValueOrTypeInModule.completionListContains('mod1ecls', 'class mod1.mod1ecls'); - verifyValueOrTypeInModule.completionListContains('mod1emod', 'namespace mod1.mod1emod'); + verifyValueOrTypeInModule.completionListContains('mod1ecls', `class ${mod1Dot}mod1ecls`); + verifyValueOrTypeInModule.completionListContains('mod1emod', `namespace ${mod1Dot}mod1emod`); verifyValueOrTypeInModule.completionListContains('mod2', 'namespace mod2'); verifyValueOrTypeInModule.completionListContains('shwcls', 'class shwcls'); @@ -295,12 +296,12 @@ function goToMarkAndGeneralVerify(marker: string, { isClassScope, isTypeLocation } // from mod1 -goToMarkAndGeneralVerify('mod1'); +goToMarkAndGeneralVerify('mod1', { insideMod1: true }); // from mod1 in type position -goToMarkAndGeneralVerify('mod1Type', { isTypeLocation: true }); +goToMarkAndGeneralVerify('mod1Type', { isTypeLocation: true, insideMod1: true }); // from function in mod1 -goToMarkAndGeneralVerify('function'); +goToMarkAndGeneralVerify('function', { insideMod1: true }); verify.completionListContains('bar', '(local var) bar: number'); verify.completionListContains('foob', '(local function) foob(): void'); @@ -310,34 +311,34 @@ goToMarkAndGeneralVerify('class', { isClassScope: true }); //verify.not.completionListContains('ceVar'); // from interface in mod1 -goToMarkAndGeneralVerify('interface'); +goToMarkAndGeneralVerify('interface', { insideMod1: true }); // from namespace in mod1 verifyNamespaceInMod1('namespace'); verifyNamespaceInMod1('namespaceType', /*isTypeLocation*/ true); function verifyNamespaceInMod1(marker: string, isTypeLocation?: boolean) { - goToMarkAndGeneralVerify(marker, { isTypeLocation }); + goToMarkAndGeneralVerify(marker, { isTypeLocation, insideMod1: true }); const { verifyValue, verifyType, verifyValueOrType, verifyNotValueOrType } = getVerify(isTypeLocation); verifyValue.completionListContains('m1X', 'var m1X: number'); verifyValue.completionListContains('m1Func', 'function m1Func(): void'); - verifyValue.completionListContains('m1eX', 'var mod1mod.m1eX: number'); - verifyValue.completionListContains('m1eFunc', 'function mod1mod.m1eFunc(): void'); + verifyValue.completionListContains('m1eX', 'var m1eX: number'); + verifyValue.completionListContains('m1eFunc', 'function m1eFunc(): void'); verifyType.completionListContains('m1Int', 'interface m1Int'); - verifyType.completionListContains('m1eInt', 'interface mod1mod.m1eInt'); + verifyType.completionListContains('m1eInt', 'interface m1eInt'); verifyValueOrType.completionListContains('m1Class', 'class m1Class'); - verifyValueOrType.completionListContains('m1eClass', 'class mod1mod.m1eClass'); + verifyValueOrType.completionListContains('m1eClass', 'class m1eClass'); verifyNotValueOrType.completionListContains('m1Mod', 'namespace m1Mod'); - verifyNotValueOrType.completionListContains('m1eMod', 'namespace mod1mod.m1eMod'); + verifyNotValueOrType.completionListContains('m1eMod', 'namespace m1eMod'); } // from exported function in mod1 -goToMarkAndGeneralVerify('exportedFunction'); +goToMarkAndGeneralVerify('exportedFunction', { insideMod1: true }); verify.completionListContains('bar', '(local var) bar: number'); verify.completionListContains('foob', '(local function) foob(): void'); @@ -347,27 +348,27 @@ verify.not.completionListContains('ceFunc'); verify.not.completionListContains('ceVar'); // from exported interface in mod1 -goToMarkAndGeneralVerify('exportedInterface'); +goToMarkAndGeneralVerify('exportedInterface', { insideMod1: true }); // from exported namespace in mod1 verifyExportedNamespace('exportedNamespace'); verifyExportedNamespace('exportedNamespaceType', /*isTypeLocation*/ true); function verifyExportedNamespace(marker: string, isTypeLocation?: boolean) { - goToMarkAndGeneralVerify(marker, { isTypeLocation }); + goToMarkAndGeneralVerify(marker, { isTypeLocation, insideMod1: true }); const { verifyValue, verifyType, verifyValueOrType, verifyNotValueOrType } = getVerify(isTypeLocation); verifyValue.completionListContains('mX', 'var mX: number'); verifyValue.completionListContains('mFunc', 'function mFunc(): void'); - verifyValue.completionListContains('meX', 'var mod1.mod1emod.meX: number'); - verifyValue.completionListContains('meFunc', 'function mod1.mod1emod.meFunc(): void'); + verifyValue.completionListContains('meX', 'var meX: number'); + verifyValue.completionListContains('meFunc', 'function meFunc(): void'); verifyType.completionListContains('mInt', 'interface mInt'); - verifyType.completionListContains('meInt', 'interface mod1.mod1emod.meInt'); + verifyType.completionListContains('meInt', 'interface meInt'); verifyValueOrType.completionListContains('mClass', 'class mClass'); - verifyValueOrType.completionListContains('meClass', 'class mod1.mod1emod.meClass'); + verifyValueOrType.completionListContains('meClass', 'class meClass'); verifyNotValueOrType.completionListContains('mMod', 'namespace mMod'); - verifyNotValueOrType.completionListContains('meMod', 'namespace mod1.mod1emod.meMod'); + verifyNotValueOrType.completionListContains('meMod', 'namespace meMod'); } // from extended namespace @@ -380,7 +381,7 @@ function verifyExtendedNamespace(marker: string, isTypeLocation?: boolean) { verifyValue.completionListContains('mod1evar', 'var mod1.mod1evar: number'); verifyValue.completionListContains('mod1efn', 'function mod1.mod1efn(): void'); - verifyValue.completionListContains('mod1eexvar', 'var mod1.mod1eexvar: number'); + verifyValue.completionListContains('mod1eexvar', 'var mod1eexvar: number'); verifyValue.completionListContains('mod3', 'namespace mod3'); verifyValue.completionListContains('shwvar', 'var shwvar: number'); verifyValue.completionListContains('shwfn', 'function shwfn(): void'); @@ -415,4 +416,4 @@ function verifyExtendedNamespace(marker: string, isTypeLocation?: boolean) { verify.not.completionListContains('sivar'); verify.not.completionListContains('sifn'); verify.not.completionListContains('mod2eexvar'); -} \ No newline at end of file +} diff --git a/tests/cases/fourslash/completionsDefaultExport.ts b/tests/cases/fourslash/completionsDefaultExport.ts new file mode 100644 index 00000000000..82bcefac403 --- /dev/null +++ b/tests/cases/fourslash/completionsDefaultExport.ts @@ -0,0 +1,11 @@ +/// + +// @Filename: /a.ts +////export default function f() {} + +// @Filename: /b.ts +////import * as a from "./a"; +////a./**/; + +goTo.marker(); +verify.completionListContains("default", "function f(): void"); diff --git a/tests/cases/fourslash/completionsNamespaceMergedWithClass.ts b/tests/cases/fourslash/completionsNamespaceMergedWithClass.ts new file mode 100644 index 00000000000..414a233bdb1 --- /dev/null +++ b/tests/cases/fourslash/completionsNamespaceMergedWithClass.ts @@ -0,0 +1,21 @@ +/// + +////class C { +//// static m() { } +////} +//// +////class D extends C {} +////namespace D { +//// export type T = number; +////} +//// +////let x: D./*type*/; +////D./*value*/ + +goTo.marker("type"); +verify.completionListContains("T"); +verify.not.completionListContains("m"); + +goTo.marker("value"); +verify.not.completionListContains("T"); +verify.completionListContains("m"); diff --git a/tests/cases/fourslash/completionsNamespaceMergedWithObject.ts b/tests/cases/fourslash/completionsNamespaceMergedWithObject.ts new file mode 100644 index 00000000000..cd799981ee7 --- /dev/null +++ b/tests/cases/fourslash/completionsNamespaceMergedWithObject.ts @@ -0,0 +1,16 @@ +/// + +////namespace N { +//// export type T = number; +////} +////const N = { m() {} }; +////let x: N./*type*/; +////N./*value*/; + +goTo.marker("type"); +verify.completionListContains("T"); +verify.not.completionListContains("m"); + +goTo.marker("value"); +verify.not.completionListContains("T"); +verify.completionListContains("m"); diff --git a/tests/cases/fourslash/exportDefaultFunction.ts b/tests/cases/fourslash/exportDefaultFunction.ts index 859d9641acd..42ddced6994 100644 --- a/tests/cases/fourslash/exportDefaultFunction.ts +++ b/tests/cases/fourslash/exportDefaultFunction.ts @@ -9,4 +9,4 @@ goTo.marker('1'); verify.completionListContains("func", "function func(): void", /*documentation*/ undefined, "function"); goTo.marker('2'); -verify.completionListContains("func", "function func(): void", /*documentation*/ undefined, "function"); \ No newline at end of file +verify.completionListContains("func", "function func(): void", /*documentation*/ undefined, "function"); diff --git a/tests/cases/fourslash/findAllRefsForDefaultExport02.ts b/tests/cases/fourslash/findAllRefsForDefaultExport02.ts index 558bbd2de85..6ad8b29b5ce 100644 --- a/tests/cases/fourslash/findAllRefsForDefaultExport02.ts +++ b/tests/cases/fourslash/findAllRefsForDefaultExport02.ts @@ -15,8 +15,9 @@ const ranges = test.ranges(); const [r0, r1, r2, r3, r4] = ranges; const fnRanges = [r0, r1, r2, r3]; -verify.singleReferenceGroup("function DefaultExportedFunction(): () => typeof DefaultExportedFunction", fnRanges); +const fn = "function DefaultExportedFunction(): () => typeof DefaultExportedFunction"; +verify.singleReferenceGroup(fn, fnRanges); // The namespace and function do not merge, // so the namespace should be all alone. -verify.singleReferenceGroup("namespace DefaultExportedFunction", [r4]); +verify.singleReferenceGroup(`namespace DefaultExportedFunction\n${fn}`, [r4]); diff --git a/tests/cases/fourslash/findAllRefsForDefaultExport08.ts b/tests/cases/fourslash/findAllRefsForDefaultExport08.ts index b29060f6099..5583ba34c95 100644 --- a/tests/cases/fourslash/findAllRefsForDefaultExport08.ts +++ b/tests/cases/fourslash/findAllRefsForDefaultExport08.ts @@ -10,6 +10,8 @@ ////namespace [|{| "isWriteAccess": true, "isDefinition": true |}DefaultExportedClass|] { ////} +verify.noErrors(); + // The namespace and class do not merge, // so the namespace should be all alone. -verify.singleReferenceGroup("namespace DefaultExportedClass"); +verify.singleReferenceGroup("class DefaultExportedClass\nnamespace DefaultExportedClass"); diff --git a/tests/cases/fourslash/formattingOnEnter.ts b/tests/cases/fourslash/formattingOnEnter.ts index 1c0915e839e..0e7d5af1a6d 100644 --- a/tests/cases/fourslash/formattingOnEnter.ts +++ b/tests/cases/fourslash/formattingOnEnter.ts @@ -6,4 +6,8 @@ goTo.marker(); edit.insertLine(""); -verify.currentLineContentIs('class bar {'); +verify.currentFileContentIs( +`class foo { } +class bar { +} +// new line here`); diff --git a/tests/cases/fourslash/fourslash.ts b/tests/cases/fourslash/fourslash.ts index 767701a2af6..59cb881ab7b 100644 --- a/tests/cases/fourslash/fourslash.ts +++ b/tests/cases/fourslash/fourslash.ts @@ -116,6 +116,7 @@ declare namespace FourSlashInterface { ranges(): Range[]; rangesByText(): ts.Map; markerByName(s: string): Marker; + symbolsInScope(range: Range): any[]; } class goTo { marker(name?: string | Marker): void; @@ -192,6 +193,7 @@ declare namespace FourSlashInterface { verifyGetEmitOutputContentsForCurrentFile(expected: ts.OutputFile[]): void; noReferences(markerNameOrRange?: string | Range): void; symbolAtLocation(startRange: Range, ...declarationRanges: Range[]): void; + typeOfSymbolAtLocation(range: Range, symbol: any, expected: string): void; /** * @deprecated, prefer 'referenceGroups' * Like `referencesAre`, but goes to `start` first. diff --git a/tests/cases/fourslash/protoVarInContexutalObjectLiteral.ts b/tests/cases/fourslash/protoVarInContextualObjectLiteral.ts similarity index 100% rename from tests/cases/fourslash/protoVarInContexutalObjectLiteral.ts rename to tests/cases/fourslash/protoVarInContextualObjectLiteral.ts diff --git a/tests/cases/fourslash/quickInfoOnNarrowedTypeInModule.ts b/tests/cases/fourslash/quickInfoOnNarrowedTypeInModule.ts index 25e6cd4c68e..54a80194f37 100644 --- a/tests/cases/fourslash/quickInfoOnNarrowedTypeInModule.ts +++ b/tests/cases/fourslash/quickInfoOnNarrowedTypeInModule.ts @@ -7,7 +7,7 @@ //// var num: number; //// var str: string; //// if (typeof /*1*/nonExportedStrOrNum === "number") { -//// num = /*2*/nonExportedStrOrNum; +//// num = /*2*/nonExportedStrOrNum; //// } //// else { //// str = /*3*/nonExportedStrOrNum.length; @@ -40,15 +40,15 @@ verify.completionListContains("nonExportedStrOrNum", "var nonExportedStrOrNum: s goTo.marker('4'); verify.quickInfoIs('var m.exportedStrOrNum: string | number'); -verify.completionListContains("exportedStrOrNum", "var m.exportedStrOrNum: string | number"); +verify.completionListContains("exportedStrOrNum", "var exportedStrOrNum: string | number"); goTo.marker('5'); verify.quickInfoIs('var m.exportedStrOrNum: number'); -verify.completionListContains("exportedStrOrNum", "var m.exportedStrOrNum: number"); +verify.completionListContains("exportedStrOrNum", "var exportedStrOrNum: number"); goTo.marker('6'); verify.quickInfoIs('var m.exportedStrOrNum: string'); -verify.completionListContains("exportedStrOrNum", "var m.exportedStrOrNum: string"); +verify.completionListContains("exportedStrOrNum", "var exportedStrOrNum: string"); goTo.marker('7'); verify.quickInfoIs('var m.exportedStrOrNum: string | number'); diff --git a/tests/cases/fourslash/typeOfSymbol_localSymbolOfExport.ts b/tests/cases/fourslash/typeOfSymbol_localSymbolOfExport.ts new file mode 100644 index 00000000000..4d23babbd21 --- /dev/null +++ b/tests/cases/fourslash/typeOfSymbol_localSymbolOfExport.ts @@ -0,0 +1,12 @@ +/// + +////export function f() {} +////[|1|]; + +const ranges = test.ranges(); +const symbolsInScope = test.symbolsInScope(ranges[0]); +const f = symbolsInScope.find(s => s.name === "f"); +if (f === undefined) throw new Error("'f' not in scope"); +if (f.exportSymbol === undefined) throw new Error("Expected to get the local symbol"); + +verify.typeOfSymbolAtLocation(ranges[0], f, "() => void"); diff --git a/tests/cases/fourslash/unusedImports13FS.ts b/tests/cases/fourslash/unusedImports13FS.ts new file mode 100644 index 00000000000..4e19f4d7f1a --- /dev/null +++ b/tests/cases/fourslash/unusedImports13FS.ts @@ -0,0 +1,12 @@ +/// + +// @noUnusedLocals: true +// @Filename: file2.ts +//// [| import A, { x } from './a'; |] +//// console.log(A); + +// @Filename: file1.ts +//// export default 10; +//// export var x = 10; + +verify.rangeAfterCodeFix("import A from './a';"); \ No newline at end of file diff --git a/tests/cases/fourslash/unusedImports14FS.ts b/tests/cases/fourslash/unusedImports14FS.ts new file mode 100644 index 00000000000..75124802d35 --- /dev/null +++ b/tests/cases/fourslash/unusedImports14FS.ts @@ -0,0 +1,15 @@ +/// + +// @noUnusedLocals: true +// @Filename: file2.ts +//// [| import /* 1 */ A /* 2 */, /* 3 */ { /* 4 */ x /* 5 */ } /* 6 */ from './a'; |] +//// console.log(A); + +// @Filename: file1.ts +//// export default 10; +//// export var x = 10; + + +// It's ambiguous which token comment /* 6 */ applies to or whether it should be removed. +// In the current implementation the comment is left behind, but this behavior isn't a requirement. +verify.rangeAfterCodeFix("import /* 1 */ A /* 2 */ /* 6 */ from './a';"); \ No newline at end of file