diff --git a/scripts/tslint/booleanTriviaRule.ts b/scripts/tslint/booleanTriviaRule.ts index 968e377c3c5..189dafac77e 100644 --- a/scripts/tslint/booleanTriviaRule.ts +++ b/scripts/tslint/booleanTriviaRule.ts @@ -27,7 +27,7 @@ function walk(ctx: Lint.WalkContext): void { /** Skip certain function/method names whose parameter names are not informative. */ function shouldIgnoreCalledExpression(expression: ts.Expression): boolean { if (expression.kind === ts.SyntaxKind.PropertyAccessExpression) { - const methodName = (expression as ts.PropertyAccessExpression).name.text; + const methodName = (expression as ts.PropertyAccessExpression).name.text as string; if (methodName.indexOf("set") === 0) { return true; } @@ -44,7 +44,7 @@ function walk(ctx: Lint.WalkContext): void { } } else if (expression.kind === ts.SyntaxKind.Identifier) { - const functionName = (expression as ts.Identifier).text; + const functionName = (expression as ts.Identifier).text as string; if (functionName.indexOf("set") === 0) { return true; } diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 059e21b9f6a..ee0a3a90d7e 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -56,7 +56,6 @@ namespace ts { let enumCount = 0; let symbolInstantiationDepth = 0; - const emptyArray: any[] = []; const emptySymbols = createSymbolTable(); const compilerOptions = host.getCompilerOptions(); @@ -84,9 +83,9 @@ namespace ts { // extra cost of calling `getParseTreeNode` when calling these functions from inside the // checker. const checker: TypeChecker = { - getNodeCount: () => sum(host.getSourceFiles(), "nodeCount"), - getIdentifierCount: () => sum(host.getSourceFiles(), "identifierCount"), - getSymbolCount: () => sum(host.getSourceFiles(), "symbolCount") + symbolCount, + getNodeCount: () => sum<"nodeCount">(host.getSourceFiles(), "nodeCount"), + getIdentifierCount: () => sum<"identifierCount">(host.getSourceFiles(), "identifierCount"), + getSymbolCount: () => sum<"symbolCount">(host.getSourceFiles(), "symbolCount") + symbolCount, getTypeCount: () => typeCount, isUndefinedSymbol: symbol => symbol === undefinedSymbol, isArgumentsSymbol: symbol => symbol === argumentsSymbol, @@ -8735,11 +8734,12 @@ namespace ts { containingMessageChain?: DiagnosticMessageChain): boolean { let errorInfo: DiagnosticMessageChain; + let maybeKeys: string[]; let sourceStack: Type[]; let targetStack: Type[]; - let maybeStack: Map[]; - let expandingFlags: number; + let maybeCount = 0; let depth = 0; + let expandingFlags = 0; let overflow = false; let isIntersectionConstituent = false; @@ -9107,10 +9107,15 @@ namespace ts { return related === RelationComparisonResult.Succeeded ? Ternary.True : Ternary.False; } } - if (depth > 0) { - for (let i = 0; i < depth; i++) { + if (!maybeKeys) { + maybeKeys = []; + sourceStack = []; + targetStack = []; + } + else { + for (let i = 0; i < maybeCount; i++) { // If source and target are already being compared, consider them related with assumptions - if (maybeStack[i].get(id)) { + if (id === maybeKeys[i]) { return Ternary.Maybe; } } @@ -9119,16 +9124,11 @@ namespace ts { return Ternary.False; } } - else { - sourceStack = []; - targetStack = []; - maybeStack = []; - expandingFlags = 0; - } + const maybeStart = maybeCount; + maybeKeys[maybeCount] = id; + maybeCount++; sourceStack[depth] = source; targetStack[depth] = target; - maybeStack[depth] = createMap(); - maybeStack[depth].set(id, RelationComparisonResult.Succeeded); depth++; const saveExpandingFlags = expandingFlags; if (!(expandingFlags & 1) && isDeeplyNestedType(source, sourceStack, depth)) expandingFlags |= 1; @@ -9137,15 +9137,19 @@ namespace ts { expandingFlags = saveExpandingFlags; depth--; if (result) { - const maybeCache = maybeStack[depth]; - // If result is definitely true, copy assumptions to global cache, else copy to next level up - const destinationCache = (result === Ternary.True || depth === 0) ? relation : maybeStack[depth - 1]; - copyEntries(maybeCache, destinationCache); + if (result === Ternary.True || depth === 0) { + // If result is definitely true, record all maybe keys as having succeeded + for (let i = maybeStart; i < maybeCount; i++) { + relation.set(maybeKeys[i], RelationComparisonResult.Succeeded); + } + maybeCount = maybeStart; + } } else { - // A false result goes straight into global cache (when something is false under assumptions it - // will also be false without assumptions) + // A false result goes straight into global cache (when something is false under + // assumptions it will also be false without assumptions) relation.set(id, reportErrors ? RelationComparisonResult.FailedAndReported : RelationComparisonResult.Failed); + maybeCount = maybeStart; } return result; } @@ -12004,9 +12008,9 @@ namespace ts { } const localOrExportSymbol = getExportSymbolOfValueSymbolIfExported(symbol); + let declaration = localOrExportSymbol.valueDeclaration; if (localOrExportSymbol.flags & SymbolFlags.Class) { - const declaration = localOrExportSymbol.valueDeclaration; // Due to the emit for class decorators, any reference to the class from inside of the class body // must instead be rewritten to point to a temporary variable to avoid issues with the double-bind // behavior of class names in ES6. @@ -12048,7 +12052,6 @@ namespace ts { checkNestedBlockScopedBinding(node, symbol); const type = getDeclaredOrApparentType(localOrExportSymbol, node); - const declaration = localOrExportSymbol.valueDeclaration; const assignmentKind = getAssignmentTargetKind(node); if (assignmentKind) { @@ -12062,11 +12065,26 @@ namespace ts { } } + const isAlias = localOrExportSymbol.flags & SymbolFlags.Alias; + // We only narrow variables and parameters occurring in a non-assignment position. For all other // entities we simply return the declared type. - if (!(localOrExportSymbol.flags & SymbolFlags.Variable) || assignmentKind === AssignmentKind.Definite || !declaration) { + if (localOrExportSymbol.flags & SymbolFlags.Variable) { + if (assignmentKind === AssignmentKind.Definite) { + return type; + } + } + else if (isAlias) { + declaration = find(symbol.declarations, isSomeImportDeclaration); + } + else { return type; } + + if (!declaration) { + return type; + } + // The declaration container is the innermost function that encloses the declaration of the variable // or parameter. The flow container is the innermost function starting with which we analyze the control // flow graph to determine the control flow based type. @@ -12085,7 +12103,7 @@ namespace ts { // We only look for uninitialized variables in strict null checking mode, and only when we can analyze // the entire control flow graph from the variable's declaration (i.e. when the flow container and // declaration container are the same). - const assumeInitialized = isParameter || isOuterVariable || + const assumeInitialized = isParameter || isAlias || isOuterVariable || type !== autoType && type !== autoArrayType && (!strictNullChecks || (type.flags & TypeFlags.Any) !== 0 || isInTypeQuery(node) || node.parent.kind === SyntaxKind.ExportSpecifier) || node.parent.kind === SyntaxKind.NonNullExpression || isInAmbientContext(declaration); @@ -24771,4 +24789,19 @@ namespace ts { return isDeclarationName(name); } } + + function isSomeImportDeclaration(decl: Node): boolean { + switch (decl.kind) { + case SyntaxKind.ImportClause: // For default import + case SyntaxKind.ImportEqualsDeclaration: + case SyntaxKind.NamespaceImport: + case SyntaxKind.ImportSpecifier: // For rename import `x as y` + return true; + case SyntaxKind.Identifier: + // For regular import, `decl` is an Identifier under the ImportSpecifier. + return decl.parent.kind === SyntaxKind.ImportSpecifier; + default: + return false; + } + } } diff --git a/src/compiler/core.ts b/src/compiler/core.ts index 99c323d608b..745cce8a6cd 100644 --- a/src/compiler/core.ts +++ b/src/compiler/core.ts @@ -162,50 +162,6 @@ namespace ts { }; } - export function createFileMap(keyMapper: (key: string) => string): FileMap { - const files = createMap(); - return { - get, - set, - contains, - remove, - forEachValue: forEachValueInMap, - getKeys, - clear, - }; - - function forEachValueInMap(f: (key: Path, value: T) => void) { - files.forEach((file, key) => { - f(key, file); - }); - } - - function getKeys() { - return arrayFrom(files.keys()) as Path[]; - } - - // path should already be well-formed so it does not need to be normalized - function get(path: Path): T { - return files.get(keyMapper(path)); - } - - function set(path: Path, value: T) { - files.set(keyMapper(path), value); - } - - function contains(path: Path) { - return files.has(keyMapper(path)); - } - - function remove(path: Path) { - files.delete(keyMapper(path)); - } - - function clear() { - files.clear(); - } - } - export function toPath(fileName: string, basePath: string, getCanonicalFileName: (path: string) => string): Path { const nonCanonicalizedPath = isRootedDiskPath(fileName) ? normalizePath(fileName) @@ -744,7 +700,7 @@ namespace ts { return result; } - export function sum(array: any[], prop: string): number { + export function sum(array: { [x in K]: number }[], prop: K): number { let result = 0; for (const v of array) { result += v[prop]; diff --git a/src/compiler/parser.ts b/src/compiler/parser.ts index 8e72a4bb08b..4a979fb3e6f 100644 --- a/src/compiler/parser.ts +++ b/src/compiler/parser.ts @@ -22,20 +22,15 @@ namespace ts { } } - function visitNode(cbNode: (node: Node) => T, node?: Node): T | undefined { - if (node) { - return cbNode(node); - } + function visitNode(cbNode: (node: Node) => T, node: Node): T | undefined { + return node && cbNode(node); } - function visitNodeArray(cbNodes: (nodes: Node[]) => T, nodes?: Node[]): T | undefined { - if (nodes) { - return cbNodes(nodes); - } - } - - function visitEachNode(cbNode: (node: Node) => T, nodes?: Node[]): T | undefined { + function visitNodes(cbNode: (node: Node) => T, cbNodes: (node: NodeArray) => T | undefined, nodes: NodeArray): T | undefined { if (nodes) { + if (cbNodes) { + return cbNodes(nodes); + } for (const node of nodes) { const result = cbNode(node); if (result) { @@ -53,17 +48,12 @@ namespace ts { * * @param node a given node to visit its children * @param cbNode a callback to be invoked for all child nodes - * @param cbNodeArray a callback to be invoked for embedded array + * @param cbNodes a callback to be invoked for embedded array */ - export function forEachChild(node: Node, cbNode: (node: Node) => T | undefined, cbNodeArray?: (nodes: NodeArray) => T | undefined): T | undefined { - if (!node) { + export function forEachChild(node: Node, cbNode: (node: Node) => T | undefined, cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { + if (!node || node.kind <= SyntaxKind.LastToken) { return; } - // The visitXXX functions could be written as local functions that close over the cbNode and cbNodeArray - // callback parameters, but that causes a closure allocation for each invocation with noticeable effects - // on performance. - const visitNodes: (cb: ((node?: Node) => T | undefined) | ((node?: Node[]) => T | undefined), nodes?: Node[]) => T | undefined = cbNodeArray ? visitNodeArray : visitEachNode; - const cbNodes = cbNodeArray || cbNode; switch (node.kind) { case SyntaxKind.QualifiedName: return visitNode(cbNode, (node).left) || @@ -74,8 +64,8 @@ namespace ts { visitNode(cbNode, (node).default) || visitNode(cbNode, (node).expression); case SyntaxKind.ShorthandPropertyAssignment: - return visitNodes(cbNodes, node.decorators) || - visitNodes(cbNodes, node.modifiers) || + return visitNodes(cbNode, cbNodes, node.decorators) || + visitNodes(cbNode, cbNodes, node.modifiers) || visitNode(cbNode, (node).name) || visitNode(cbNode, (node).questionToken) || visitNode(cbNode, (node).equalsToken) || @@ -88,8 +78,8 @@ namespace ts { case SyntaxKind.PropertyAssignment: case SyntaxKind.VariableDeclaration: case SyntaxKind.BindingElement: - return visitNodes(cbNodes, node.decorators) || - visitNodes(cbNodes, node.modifiers) || + return visitNodes(cbNode, cbNodes, node.decorators) || + visitNodes(cbNode, cbNodes, node.modifiers) || visitNode(cbNode, (node).propertyName) || visitNode(cbNode, (node).dotDotDotToken) || visitNode(cbNode, (node).name) || @@ -101,10 +91,10 @@ namespace ts { case SyntaxKind.CallSignature: case SyntaxKind.ConstructSignature: case SyntaxKind.IndexSignature: - return visitNodes(cbNodes, node.decorators) || - visitNodes(cbNodes, node.modifiers) || - visitNodes(cbNodes, (node).typeParameters) || - visitNodes(cbNodes, (node).parameters) || + return visitNodes(cbNode, cbNodes, node.decorators) || + visitNodes(cbNode, cbNodes, node.modifiers) || + visitNodes(cbNode, cbNodes, (node).typeParameters) || + visitNodes(cbNode, cbNodes, (node).parameters) || visitNode(cbNode, (node).type); case SyntaxKind.MethodDeclaration: case SyntaxKind.MethodSignature: @@ -114,33 +104,33 @@ namespace ts { case SyntaxKind.FunctionExpression: case SyntaxKind.FunctionDeclaration: case SyntaxKind.ArrowFunction: - return visitNodes(cbNodes, node.decorators) || - visitNodes(cbNodes, node.modifiers) || + return visitNodes(cbNode, cbNodes, node.decorators) || + visitNodes(cbNode, cbNodes, node.modifiers) || visitNode(cbNode, (node).asteriskToken) || visitNode(cbNode, (node).name) || visitNode(cbNode, (node).questionToken) || - visitNodes(cbNodes, (node).typeParameters) || - visitNodes(cbNodes, (node).parameters) || + visitNodes(cbNode, cbNodes, (node).typeParameters) || + visitNodes(cbNode, cbNodes, (node).parameters) || visitNode(cbNode, (node).type) || visitNode(cbNode, (node).equalsGreaterThanToken) || visitNode(cbNode, (node).body); case SyntaxKind.TypeReference: return visitNode(cbNode, (node).typeName) || - visitNodes(cbNodes, (node).typeArguments); + visitNodes(cbNode, cbNodes, (node).typeArguments); case SyntaxKind.TypePredicate: return visitNode(cbNode, (node).parameterName) || visitNode(cbNode, (node).type); case SyntaxKind.TypeQuery: return visitNode(cbNode, (node).exprName); case SyntaxKind.TypeLiteral: - return visitNodes(cbNodes, (node).members); + return visitNodes(cbNode, cbNodes, (node).members); case SyntaxKind.ArrayType: return visitNode(cbNode, (node).elementType); case SyntaxKind.TupleType: - return visitNodes(cbNodes, (node).elementTypes); + return visitNodes(cbNode, cbNodes, (node).elementTypes); case SyntaxKind.UnionType: case SyntaxKind.IntersectionType: - return visitNodes(cbNodes, (node).types); + return visitNodes(cbNode, cbNodes, (node).types); case SyntaxKind.ParenthesizedType: case SyntaxKind.TypeOperator: return visitNode(cbNode, (node).type); @@ -156,11 +146,11 @@ namespace ts { return visitNode(cbNode, (node).literal); case SyntaxKind.ObjectBindingPattern: case SyntaxKind.ArrayBindingPattern: - return visitNodes(cbNodes, (node).elements); + return visitNodes(cbNode, cbNodes, (node).elements); case SyntaxKind.ArrayLiteralExpression: - return visitNodes(cbNodes, (node).elements); + return visitNodes(cbNode, cbNodes, (node).elements); case SyntaxKind.ObjectLiteralExpression: - return visitNodes(cbNodes, (node).properties); + return visitNodes(cbNode, cbNodes, (node).properties); case SyntaxKind.PropertyAccessExpression: return visitNode(cbNode, (node).expression) || visitNode(cbNode, (node).name); @@ -170,8 +160,8 @@ namespace ts { case SyntaxKind.CallExpression: case SyntaxKind.NewExpression: return visitNode(cbNode, (node).expression) || - visitNodes(cbNodes, (node).typeArguments) || - visitNodes(cbNodes, (node).arguments); + visitNodes(cbNode, cbNodes, (node).typeArguments) || + visitNodes(cbNode, cbNodes, (node).arguments); case SyntaxKind.TaggedTemplateExpression: return visitNode(cbNode, (node).tag) || visitNode(cbNode, (node).template); @@ -216,16 +206,16 @@ namespace ts { return visitNode(cbNode, (node).expression); case SyntaxKind.Block: case SyntaxKind.ModuleBlock: - return visitNodes(cbNodes, (node).statements); + return visitNodes(cbNode, cbNodes, (node).statements); case SyntaxKind.SourceFile: - return visitNodes(cbNodes, (node).statements) || + return visitNodes(cbNode, cbNodes, (node).statements) || visitNode(cbNode, (node).endOfFileToken); case SyntaxKind.VariableStatement: - return visitNodes(cbNodes, node.decorators) || - visitNodes(cbNodes, node.modifiers) || + return visitNodes(cbNode, cbNodes, node.decorators) || + visitNodes(cbNode, cbNodes, node.modifiers) || visitNode(cbNode, (node).declarationList); case SyntaxKind.VariableDeclarationList: - return visitNodes(cbNodes, (node).declarations); + return visitNodes(cbNode, cbNodes, (node).declarations); case SyntaxKind.ExpressionStatement: return visitNode(cbNode, (node).expression); case SyntaxKind.IfStatement: @@ -264,12 +254,12 @@ namespace ts { return visitNode(cbNode, (node).expression) || visitNode(cbNode, (node).caseBlock); case SyntaxKind.CaseBlock: - return visitNodes(cbNodes, (node).clauses); + return visitNodes(cbNode, cbNodes, (node).clauses); case SyntaxKind.CaseClause: return visitNode(cbNode, (node).expression) || - visitNodes(cbNodes, (node).statements); + visitNodes(cbNode, cbNodes, (node).statements); case SyntaxKind.DefaultClause: - return visitNodes(cbNodes, (node).statements); + return visitNodes(cbNode, cbNodes, (node).statements); case SyntaxKind.LabeledStatement: return visitNode(cbNode, (node).label) || visitNode(cbNode, (node).statement); @@ -286,46 +276,46 @@ namespace ts { return visitNode(cbNode, (node).expression); case SyntaxKind.ClassDeclaration: case SyntaxKind.ClassExpression: - return visitNodes(cbNodes, node.decorators) || - visitNodes(cbNodes, node.modifiers) || + return visitNodes(cbNode, cbNodes, node.decorators) || + visitNodes(cbNode, cbNodes, node.modifiers) || visitNode(cbNode, (node).name) || - visitNodes(cbNodes, (node).typeParameters) || - visitNodes(cbNodes, (node).heritageClauses) || - visitNodes(cbNodes, (node).members); + visitNodes(cbNode, cbNodes, (node).typeParameters) || + visitNodes(cbNode, cbNodes, (node).heritageClauses) || + visitNodes(cbNode, cbNodes, (node).members); case SyntaxKind.InterfaceDeclaration: - return visitNodes(cbNodes, node.decorators) || - visitNodes(cbNodes, node.modifiers) || + return visitNodes(cbNode, cbNodes, node.decorators) || + visitNodes(cbNode, cbNodes, node.modifiers) || visitNode(cbNode, (node).name) || - visitNodes(cbNodes, (node).typeParameters) || - visitNodes(cbNodes, (node).heritageClauses) || - visitNodes(cbNodes, (node).members); + visitNodes(cbNode, cbNodes, (node).typeParameters) || + visitNodes(cbNode, cbNodes, (node).heritageClauses) || + visitNodes(cbNode, cbNodes, (node).members); case SyntaxKind.TypeAliasDeclaration: - return visitNodes(cbNodes, node.decorators) || - visitNodes(cbNodes, node.modifiers) || + return visitNodes(cbNode, cbNodes, node.decorators) || + visitNodes(cbNode, cbNodes, node.modifiers) || visitNode(cbNode, (node).name) || - visitNodes(cbNodes, (node).typeParameters) || + visitNodes(cbNode, cbNodes, (node).typeParameters) || visitNode(cbNode, (node).type); case SyntaxKind.EnumDeclaration: - return visitNodes(cbNodes, node.decorators) || - visitNodes(cbNodes, node.modifiers) || + return visitNodes(cbNode, cbNodes, node.decorators) || + visitNodes(cbNode, cbNodes, node.modifiers) || visitNode(cbNode, (node).name) || - visitNodes(cbNodes, (node).members); + visitNodes(cbNode, cbNodes, (node).members); case SyntaxKind.EnumMember: return visitNode(cbNode, (node).name) || visitNode(cbNode, (node).initializer); case SyntaxKind.ModuleDeclaration: - return visitNodes(cbNodes, node.decorators) || - visitNodes(cbNodes, node.modifiers) || + return visitNodes(cbNode, cbNodes, node.decorators) || + visitNodes(cbNode, cbNodes, node.modifiers) || visitNode(cbNode, (node).name) || visitNode(cbNode, (node).body); case SyntaxKind.ImportEqualsDeclaration: - return visitNodes(cbNodes, node.decorators) || - visitNodes(cbNodes, node.modifiers) || + return visitNodes(cbNode, cbNodes, node.decorators) || + visitNodes(cbNode, cbNodes, node.modifiers) || visitNode(cbNode, (node).name) || visitNode(cbNode, (node).moduleReference); case SyntaxKind.ImportDeclaration: - return visitNodes(cbNodes, node.decorators) || - visitNodes(cbNodes, node.modifiers) || + return visitNodes(cbNode, cbNodes, node.decorators) || + visitNodes(cbNode, cbNodes, node.modifiers) || visitNode(cbNode, (node).importClause) || visitNode(cbNode, (node).moduleSpecifier); case SyntaxKind.ImportClause: @@ -338,10 +328,10 @@ namespace ts { return visitNode(cbNode, (node).name); case SyntaxKind.NamedImports: case SyntaxKind.NamedExports: - return visitNodes(cbNodes, (node).elements); + return visitNodes(cbNode, cbNodes, (node).elements); case SyntaxKind.ExportDeclaration: - return visitNodes(cbNodes, node.decorators) || - visitNodes(cbNodes, node.modifiers) || + return visitNodes(cbNode, cbNodes, node.decorators) || + visitNodes(cbNode, cbNodes, node.modifiers) || visitNode(cbNode, (node).exportClause) || visitNode(cbNode, (node).moduleSpecifier); case SyntaxKind.ImportSpecifier: @@ -349,37 +339,37 @@ namespace ts { return visitNode(cbNode, (node).propertyName) || visitNode(cbNode, (node).name); case SyntaxKind.ExportAssignment: - return visitNodes(cbNodes, node.decorators) || - visitNodes(cbNodes, node.modifiers) || + return visitNodes(cbNode, cbNodes, node.decorators) || + visitNodes(cbNode, cbNodes, node.modifiers) || visitNode(cbNode, (node).expression); case SyntaxKind.TemplateExpression: - return visitNode(cbNode, (node).head) || visitNodes(cbNodes, (node).templateSpans); + return visitNode(cbNode, (node).head) || visitNodes(cbNode, cbNodes, (node).templateSpans); case SyntaxKind.TemplateSpan: return visitNode(cbNode, (node).expression) || visitNode(cbNode, (node).literal); case SyntaxKind.ComputedPropertyName: return visitNode(cbNode, (node).expression); case SyntaxKind.HeritageClause: - return visitNodes(cbNodes, (node).types); + return visitNodes(cbNode, cbNodes, (node).types); case SyntaxKind.ExpressionWithTypeArguments: return visitNode(cbNode, (node).expression) || - visitNodes(cbNodes, (node).typeArguments); + visitNodes(cbNode, cbNodes, (node).typeArguments); case SyntaxKind.ExternalModuleReference: return visitNode(cbNode, (node).expression); case SyntaxKind.MissingDeclaration: - return visitNodes(cbNodes, node.decorators); + return visitNodes(cbNode, cbNodes, node.decorators); case SyntaxKind.CommaListExpression: - return visitNodes(cbNodes, (node).elements); + return visitNodes(cbNode, cbNodes, (node).elements); case SyntaxKind.JsxElement: return visitNode(cbNode, (node).openingElement) || - visitNodes(cbNodes, (node).children) || + visitNodes(cbNode, cbNodes, (node).children) || visitNode(cbNode, (node).closingElement); case SyntaxKind.JsxSelfClosingElement: case SyntaxKind.JsxOpeningElement: return visitNode(cbNode, (node).tagName) || visitNode(cbNode, (node).attributes); case SyntaxKind.JsxAttributes: - return visitNodes(cbNodes, (node).properties); + return visitNodes(cbNode, cbNodes, (node).properties); case SyntaxKind.JsxAttribute: return visitNode(cbNode, (node).name) || visitNode(cbNode, (node).initializer); @@ -394,9 +384,9 @@ namespace ts { case SyntaxKind.JSDocTypeExpression: return visitNode(cbNode, (node).type); case SyntaxKind.JSDocUnionType: - return visitNodes(cbNodes, (node).types); + return visitNodes(cbNode, cbNodes, (node).types); case SyntaxKind.JSDocTupleType: - return visitNodes(cbNodes, (node).types); + return visitNodes(cbNode, cbNodes, (node).types); case SyntaxKind.JSDocArrayType: return visitNode(cbNode, (node).elementType); case SyntaxKind.JSDocNonNullableType: @@ -407,11 +397,11 @@ namespace ts { return visitNode(cbNode, (node).literal); case SyntaxKind.JSDocTypeReference: return visitNode(cbNode, (node).name) || - visitNodes(cbNodes, (node).typeArguments); + visitNodes(cbNode, cbNodes, (node).typeArguments); case SyntaxKind.JSDocOptionalType: return visitNode(cbNode, (node).type); case SyntaxKind.JSDocFunctionType: - return visitNodes(cbNodes, (node).parameters) || + return visitNodes(cbNode, cbNodes, (node).parameters) || visitNode(cbNode, (node).type); case SyntaxKind.JSDocVariadicType: return visitNode(cbNode, (node).type); @@ -423,7 +413,7 @@ namespace ts { return visitNode(cbNode, (node).name) || visitNode(cbNode, (node).type); case SyntaxKind.JSDocComment: - return visitNodes(cbNodes, (node).tags); + return visitNodes(cbNode, cbNodes, (node).tags); case SyntaxKind.JSDocParameterTag: return visitNode(cbNode, (node).preParameterName) || visitNode(cbNode, (node).typeExpression) || @@ -435,14 +425,14 @@ namespace ts { case SyntaxKind.JSDocAugmentsTag: return visitNode(cbNode, (node).typeExpression); case SyntaxKind.JSDocTemplateTag: - return visitNodes(cbNodes, (node).typeParameters); + return visitNodes(cbNode, cbNodes, (node).typeParameters); case SyntaxKind.JSDocTypedefTag: return visitNode(cbNode, (node).typeExpression) || visitNode(cbNode, (node).fullName) || visitNode(cbNode, (node).name) || visitNode(cbNode, (node).jsDocTypeLiteral); case SyntaxKind.JSDocTypeLiteral: - return visitNodes(cbNodes, (node).jsDocPropertyTags); + return visitNodes(cbNode, cbNodes, (node).jsDocPropertyTags); case SyntaxKind.JSDocPropertyTag: return visitNode(cbNode, (node).typeExpression) || visitNode(cbNode, (node).name); diff --git a/src/compiler/program.ts b/src/compiler/program.ts index 916d32b416a..97f6f36f3a1 100644 --- a/src/compiler/program.ts +++ b/src/compiler/program.ts @@ -441,7 +441,7 @@ namespace ts { const supportedExtensions = getSupportedExtensions(options); // Map storing if there is emit blocking diagnostics for given input - const hasEmitBlockingDiagnostics = createFileMap(getCanonicalFileName); + const hasEmitBlockingDiagnostics = createMap(); let _compilerOptionsObjectLiteralSyntax: ObjectLiteralExpression; let moduleResolutionCache: ModuleResolutionCache; @@ -475,7 +475,7 @@ namespace ts { const filesByName = createMap(); // stores 'filename -> file association' ignoring case // used to track cases when two file names differ only in casing - const filesByNameIgnoreCase = host.useCaseSensitiveFileNames() ? createFileMap(fileName => fileName.toLowerCase()) : undefined; + const filesByNameIgnoreCase = host.useCaseSensitiveFileNames() ? createMap() : undefined; const structuralIsReused = tryReuseStructureFromOldProgram(); if (structuralIsReused !== StructureIsReused.Completely) { @@ -556,6 +556,10 @@ namespace ts { return program; + function toPath(fileName: string): Path { + return ts.toPath(fileName, currentDirectory, getCanonicalFileName); + } + function getCommonSourceDirectory() { if (commonSourceDirectory === undefined) { const emittedFiles = filter(files, file => sourceFileMayBeEmitted(file, options, isSourceFileFromExternalLibrary)); @@ -934,7 +938,7 @@ namespace ts { } function isEmitBlocked(emitFileName: string): boolean { - return hasEmitBlockingDiagnostics.contains(toPath(emitFileName, currentDirectory, getCanonicalFileName)); + return hasEmitBlockingDiagnostics.has(toPath(emitFileName)); } function emitWorker(program: Program, sourceFile: SourceFile, writeFileCallback: WriteFileCallback, cancellationToken: CancellationToken, emitOnlyDtsFiles?: boolean, customTransformers?: CustomTransformers): EmitResult { @@ -993,7 +997,7 @@ namespace ts { } function getSourceFile(fileName: string): SourceFile { - return getSourceFileByPath(toPath(fileName, currentDirectory, getCanonicalFileName)); + return getSourceFileByPath(toPath(fileName)); } function getSourceFileByPath(path: Path): SourceFile { @@ -1484,7 +1488,7 @@ namespace ts { /** This should have similar behavior to 'processSourceFile' without diagnostics or mutation. */ function getSourceFileFromReference(referencingFile: SourceFile, ref: FileReference): SourceFile | undefined { - return getSourceFileFromReferenceWorker(resolveTripleslashReference(ref.fileName, referencingFile.fileName), fileName => filesByName.get(toPath(fileName, currentDirectory, getCanonicalFileName))); + return getSourceFileFromReferenceWorker(resolveTripleslashReference(ref.fileName, referencingFile.fileName), fileName => filesByName.get(toPath(fileName))); } function getSourceFileFromReferenceWorker( @@ -1528,7 +1532,7 @@ namespace ts { /** This has side effects through `findSourceFile`. */ function processSourceFile(fileName: string, isDefaultLib: boolean, refFile?: SourceFile, refPos?: number, refEnd?: number): void { getSourceFileFromReferenceWorker(fileName, - fileName => findSourceFile(fileName, toPath(fileName, currentDirectory, getCanonicalFileName), isDefaultLib, refFile, refPos, refEnd), + fileName => findSourceFile(fileName, toPath(fileName), isDefaultLib, refFile, refPos, refEnd), (diagnostic, ...args) => { fileProcessingDiagnostics.add(refFile !== undefined && refEnd !== undefined && refPos !== undefined ? createFileDiagnostic(refFile, refPos, refEnd - refPos, diagnostic, ...args) @@ -1597,13 +1601,14 @@ namespace ts { file.path = path; if (host.useCaseSensitiveFileNames()) { + const pathLowerCase = path.toLowerCase(); // for case-sensitive file systems check if we've already seen some file with similar filename ignoring case - const existingFile = filesByNameIgnoreCase.get(path); + const existingFile = filesByNameIgnoreCase.get(pathLowerCase); if (existingFile) { reportFileNamesDifferOnlyInCasingError(fileName, existingFile.fileName, refFile, refPos, refEnd); } else { - filesByNameIgnoreCase.set(path, file); + filesByNameIgnoreCase.set(pathLowerCase, file); } } @@ -1750,7 +1755,7 @@ namespace ts { modulesWithElidedImports.set(file.path, true); } else if (shouldAddFile) { - const path = toPath(resolvedFileName, currentDirectory, getCanonicalFileName); + const path = toPath(resolvedFileName); const pos = skipTrivia(file.text, file.imports[i].pos); findSourceFile(resolvedFileName, path, /*isDefaultLib*/ false, file, pos, file.imports[i].end); } @@ -1969,7 +1974,7 @@ namespace ts { // If the emit is enabled make sure that every output file is unique and not overwriting any of the input files if (!options.noEmit && !options.suppressOutputPathCheck) { const emitHost = getEmitHost(); - const emitFilesSeen = createFileMap(!host.useCaseSensitiveFileNames() ? key => key.toLocaleLowerCase() : key => key); + const emitFilesSeen = createMap(); forEachEmittedFile(emitHost, (emitFileNames) => { verifyEmitFilePath(emitFileNames.jsFilePath, emitFilesSeen); verifyEmitFilePath(emitFileNames.declarationFilePath, emitFilesSeen); @@ -1977,9 +1982,9 @@ namespace ts { } // Verify that all the emit files are unique and don't overwrite input files - function verifyEmitFilePath(emitFileName: string, emitFilesSeen: FileMap) { + function verifyEmitFilePath(emitFileName: string, emitFilesSeen: Map) { if (emitFileName) { - const emitFilePath = toPath(emitFileName, currentDirectory, getCanonicalFileName); + const emitFilePath = toPath(emitFileName); // Report error if the output overwrites input file if (filesByName.has(emitFilePath)) { let chain: DiagnosticMessageChain; @@ -1991,13 +1996,14 @@ namespace ts { blockEmittingOfFile(emitFileName, createCompilerDiagnosticFromMessageChain(chain)); } + const emitFileKey = !host.useCaseSensitiveFileNames() ? emitFilePath.toLocaleLowerCase() : emitFilePath; // Report error if multiple files write into same file - if (emitFilesSeen.contains(emitFilePath)) { + if (emitFilesSeen.has(emitFileKey)) { // Already seen the same emit file - report error blockEmittingOfFile(emitFileName, createCompilerDiagnostic(Diagnostics.Cannot_write_file_0_because_it_would_be_overwritten_by_multiple_input_files, emitFileName)); } else { - emitFilesSeen.set(emitFilePath, true); + emitFilesSeen.set(emitFileKey, true); } } } @@ -2089,7 +2095,7 @@ namespace ts { } function blockEmittingOfFile(emitFileName: string, diag: Diagnostic) { - hasEmitBlockingDiagnostics.set(toPath(emitFileName, currentDirectory, getCanonicalFileName), true); + hasEmitBlockingDiagnostics.set(toPath(emitFileName), true); programDiagnostics.add(diag); } } diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 63b50d58e4a..aefcce6d0bb 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -31,17 +31,6 @@ namespace ts { // arbitrary file name can be converted to Path via toPath function export type Path = string & { __pathBrand: any }; - export interface FileMap { - get(fileName: Path): T; - set(fileName: Path, value: T): void; - contains(fileName: Path): boolean; - remove(fileName: Path): void; - - forEachValue(f: (key: Path, v: T) => void): void; - getKeys(): Path[]; - clear(): void; - } - export interface TextRange { pos: number; end: number; @@ -527,7 +516,7 @@ namespace ts { /* @internal */ original?: Node; // The original node if this is an updated node. /* @internal */ startsOnNewLine?: boolean; // Whether a synthesized node should start on a new line (used by transforms). /* @internal */ jsDoc?: JSDoc[]; // JSDoc that directly precedes this node - /* @internal */ jsDocCache?: (JSDoc | JSDocTag)[]; // All JSDoc that applies to the node, including parent docs and @param tags + /* @internal */ jsDocCache?: JSDocTag[]; // Cache for getJSDocTags /* @internal */ symbol?: Symbol; // Symbol declared by node (initialized by binding) /* @internal */ locals?: SymbolTable; // Locals associated with node (initialized by binding) /* @internal */ nextContainer?: Node; // Next container in declaration order (initialized by binding) @@ -4350,7 +4339,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 c68e225364d..4de4c36c969 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -2,6 +2,8 @@ /* @internal */ namespace ts { + export const emptyArray: never[] = [] as never[]; + export const externalHelpersModuleNameText = "tslib"; export interface ReferencePathMatchResult { @@ -1445,39 +1447,37 @@ namespace ts { (node).parameters[0].type.kind === SyntaxKind.JSDocConstructorType; } - export function getCommentsFromJSDoc(node: Node): string[] { - return map(getJSDocs(node), doc => doc.comment); + export function hasJSDocParameterTags(node: FunctionLikeDeclaration | SignatureDeclaration): boolean { + return !!getFirstJSDocTag(node, SyntaxKind.JSDocParameterTag); } - export function hasJSDocParameterTags(node: FunctionLikeDeclaration | SignatureDeclaration) { - const parameterTags = getJSDocTags(node, SyntaxKind.JSDocParameterTag); - return parameterTags && parameterTags.length > 0; + function getFirstJSDocTag(node: Node, kind: SyntaxKind): JSDocTag | undefined { + const tags = getJSDocTags(node); + return find(tags, doc => doc.kind === kind); } - function getJSDocTags(node: Node, kind: SyntaxKind): JSDocTag[] { - return flatMap(getJSDocs(node), doc => - doc.kind === SyntaxKind.JSDocComment - ? filter((doc as JSDoc).tags, tag => tag.kind === kind) - : doc.kind === kind && doc); - } - - function getFirstJSDocTag(node: Node, kind: SyntaxKind): JSDocTag { - return node && firstOrUndefined(getJSDocTags(node, kind)); - } - - export function getJSDocs(node: Node): (JSDoc | JSDocTag)[] { + export function getAllJSDocs(node: Node): (JSDoc | JSDocTag)[] { if (isJSDocTypedefTag(node)) { return [node.parent]; } + return getJSDocCommentsAndTags(node); + } - let cache: (JSDoc | JSDocTag)[] = node.jsDocCache; - if (!cache) { - getJSDocsWorker(node); - node.jsDocCache = cache; + export function getJSDocTags(node: Node): JSDocTag[] | undefined { + let tags = node.jsDocCache; + // If cache is 'null', that means we did the work of searching for JSDoc tags and came up with nothing. + if (tags === undefined) { + node.jsDocCache = tags = flatMap(getJSDocCommentsAndTags(node), j => isJSDoc(j) ? j.tags : j); } - return cache; + return tags; + } - function getJSDocsWorker(node: Node) { + function getJSDocCommentsAndTags(node: Node): (JSDoc | JSDocTag)[] { + let result: Array | undefined; + getJSDocCommentsAndTagsWorker(node); + return result || emptyArray; + + function getJSDocCommentsAndTagsWorker(node: Node): void { const parent = node.parent; // Try to recognize this pattern when node is initializer of variable declaration and JSDoc comments are on containing variable statement. // /** @@ -1496,7 +1496,7 @@ namespace ts { isVariableOfVariableDeclarationStatement ? parent.parent : undefined; if (variableStatementNode) { - getJSDocsWorker(variableStatementNode); + getJSDocCommentsAndTagsWorker(variableStatementNode); } // Also recognize when the node is the RHS of an assignment expression @@ -1506,43 +1506,51 @@ namespace ts { (parent as BinaryExpression).operatorToken.kind === SyntaxKind.EqualsToken && parent.parent.kind === SyntaxKind.ExpressionStatement; if (isSourceOfAssignmentExpressionStatement) { - getJSDocsWorker(parent.parent); + getJSDocCommentsAndTagsWorker(parent.parent); } const isModuleDeclaration = node.kind === SyntaxKind.ModuleDeclaration && parent && parent.kind === SyntaxKind.ModuleDeclaration; const isPropertyAssignmentExpression = parent && parent.kind === SyntaxKind.PropertyAssignment; if (isModuleDeclaration || isPropertyAssignmentExpression) { - getJSDocsWorker(parent); + getJSDocCommentsAndTagsWorker(parent); } // Pull parameter comments from declaring function as well if (node.kind === SyntaxKind.Parameter) { - cache = concatenate(cache, getJSDocParameterTags(node as ParameterDeclaration)); + result = addRange(result, getJSDocParameterTags(node as ParameterDeclaration)); } if (isVariableLike(node) && node.initializer) { - cache = concatenate(cache, node.initializer.jsDoc); + result = addRange(result, node.initializer.jsDoc); } - cache = concatenate(cache, node.jsDoc); + result = addRange(result, node.jsDoc); } } - export function getJSDocParameterTags(param: ParameterDeclaration): JSDocParameterTag[] { + export function getJSDocParameterTags(param: ParameterDeclaration): JSDocParameterTag[] | undefined { const func = param.parent; - const tags = getJSDocTags(func, SyntaxKind.JSDocParameterTag) as JSDocParameterTag[]; + const tags = getJSDocTags(func); + if (!tags) return undefined; + if (!param.name) { // this is an anonymous jsdoc param from a `function(type1, type2): type3` specification - const i = func.parameters.indexOf(param); - const paramTags = filter(tags, tag => tag.kind === SyntaxKind.JSDocParameterTag); - if (paramTags && 0 <= i && i < paramTags.length) { - return [paramTags[i]]; + const paramIndex = func.parameters.indexOf(param); + Debug.assert(paramIndex !== -1); + let curParamIndex = 0; + for (const tag of tags) { + if (isJSDocParameterTag(tag)) { + if (curParamIndex === paramIndex) { + return [tag]; + } + curParamIndex++; + } } } else if (param.name.kind === SyntaxKind.Identifier) { const name = (param.name as Identifier).text; - return filter(tags, tag => tag.kind === SyntaxKind.JSDocParameterTag && tag.name.text === name); + return tags.filter((tag): tag is JSDocParameterTag => isJSDocParameterTag(tag) && tag.name.text === name) as JSDocParameterTag[]; } else { // TODO: it's a destructured parameter, so it should look up an "object type" series of multiple lines 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/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 534d73a6410..5250a8ea66c 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); } /** @@ -75,16 +75,9 @@ namespace ts.server { return ts.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 ts.createTextSpan(absolutePosition, len); } /** @@ -95,25 +88,17 @@ namespace ts.server { if (!this.svc) { return computePositionOfLineAndCharacter(this.getLineMap(), line - 1, offset - 1); } - 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) { @@ -334,7 +319,7 @@ namespace ts.server { } } - getLineInfo(line: number) { + getLineInfo(line: number): AbsolutePositionAndLineText { return this.textStorage.getLineInfo(line); } @@ -364,11 +349,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..022447e488e 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 { @@ -397,22 +395,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 }; } } @@ -502,17 +505,12 @@ 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()) { @@ -676,90 +674,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/session.ts b/src/server/session.ts index 4b4d0a02265..cdd4306c064 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) { @@ -84,15 +84,15 @@ namespace ts.server { }; } - 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 start = diag.file && convertToLocation(getLineAndCharacterOfPosition(diag.file, diag.start)); + const end = diag.file && convertToLocation(getLineAndCharacterOfPosition(diag.file, diag.start + diag.length)); const text = ts.flattenDiagnosticMessageText(diag.messageText, "\n"); const { code, source } = diag; const category = DiagnosticCategory[diag.category].toLowerCase(); @@ -555,8 +555,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)) }); } @@ -1131,32 +1131,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: ts.createTextSpanFromBounds(absolutePosition, firstNoWhiteSpacePosition), + newText: formatting.getIndentationString(preferredIndent, formatOptions) + }); } } } @@ -1514,7 +1511,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); diff --git a/src/services/jsDoc.ts b/src/services/jsDoc.ts index 63b103097aa..c5e437f20fc 100644 --- a/src/services/jsDoc.ts +++ b/src/services/jsDoc.ts @@ -53,18 +53,14 @@ namespace ts.JsDoc { // from Array - Array and Array const documentationComment = []; forEachUnique(declarations, declaration => { - const comments = getCommentsFromJSDoc(declaration); - if (!comments) { - return; - } - for (const comment of comments) { - if (comment) { + forEach(getAllJSDocs(declaration), doc => { + if (doc.comment) { if (documentationComment.length) { documentationComment.push(lineBreakPart()); } - documentationComment.push(textPart(comment)); + documentationComment.push(textPart(doc.comment)); } - } + }); }); return documentationComment; } @@ -73,18 +69,9 @@ namespace ts.JsDoc { // Only collect doc comments from duplicate declarations once. const tags: JSDocTagInfo[] = []; forEachUnique(declarations, declaration => { - const jsDocs = getJSDocs(declaration); - if (!jsDocs) { - return; - } - for (const doc of jsDocs) { - const tagsForDoc = (doc as JSDoc).tags; - if (tagsForDoc) { - tags.push(...tagsForDoc.filter(tag => tag.kind === SyntaxKind.JSDocTag).map(jsDocTag => { - return { - name: unescapeLeadingUnderscores(jsDocTag.tagName.text), - text: jsDocTag.comment - }; })); + for (const tag of getJSDocTags(declaration)) { + if (tag.kind === SyntaxKind.JSDocTag) { + tags.push({ name: unescapeLeadingUnderscores(tag.tagName.text), text: tag.comment }); } } }); diff --git a/src/services/jsTyping.ts b/src/services/jsTyping.ts index f3ee1e007cc..ff90d5bdb34 100644 --- a/src/services/jsTyping.ts +++ b/src/services/jsTyping.ts @@ -29,8 +29,6 @@ namespace ts.JsTyping { // that we are confident require typings let safeList: Map; - const EmptySafeList: Map = createMap(); - /* @internal */ export const nodeCoreModuleList: ReadonlyArray = [ "buffer", "querystring", "events", "http", "cluster", @@ -177,16 +175,14 @@ namespace ts.JsTyping { * @param fileNames are the names for source files in the project */ function getTypingNamesFromSourceFileNames(fileNames: string[]) { - if (safeList !== EmptySafeList) { - for (const j of fileNames) { - if (!hasJavaScriptFileExtension(j)) continue; + for (const j of fileNames) { + if (!hasJavaScriptFileExtension(j)) continue; - const inferredTypingName = removeFileExtension(getBaseFileName(j.toLowerCase())); - const cleanedTypingName = inferredTypingName.replace(/((?:\.|-)min(?=\.|$))|((?:-|\.)\d+)/g, ""); - const safe = safeList.get(cleanedTypingName); - if (safe !== undefined) { - addInferredTyping(safe); - } + const inferredTypingName = removeFileExtension(getBaseFileName(j.toLowerCase())); + const cleanedTypingName = inferredTypingName.replace(/((?:\.|-)min(?=\.|$))|((?:-|\.)\d+)/g, ""); + const safe = safeList.get(cleanedTypingName); + if (safe !== undefined) { + addInferredTyping(safe); } } diff --git a/src/services/utilities.ts b/src/services/utilities.ts index 90cdcfb327e..5819b5a60ac 100644 --- a/src/services/utilities.ts +++ b/src/services/utilities.ts @@ -2,7 +2,6 @@ /* @internal */ namespace ts { export const scanner: Scanner = createScanner(ScriptTarget.Latest, /*skipTrivia*/ true); - export const emptyArray: any[] = []; export const enum SemanticMeaning { None = 0x0, diff --git a/tests/baselines/reference/narrowedImports.js b/tests/baselines/reference/narrowedImports.js new file mode 100644 index 00000000000..5dba68e84e5 --- /dev/null +++ b/tests/baselines/reference/narrowedImports.js @@ -0,0 +1,43 @@ +//// [tests/cases/compiler/narrowedImports.ts] //// + +//// [a.d.ts] +declare const a0: number | undefined; +export default a0; +export const a1: number | undefined; + +//// [b.d.ts] +declare const b: number | undefined; +declare namespace b {} +export = b; + +//// [x.ts] +import a0, { a1, a1 as a2 } from "./a"; +import * as b0 from "./b"; +import b1 = require("./b"); + +let x: number; + +if (a0) x = a0; +if (a1) x = a1; +if (a2) x = a2; +if (b0) x = b0; +if (b1) x = b1; + + +//// [x.js] +"use strict"; +exports.__esModule = true; +var a_1 = require("./a"); +var b0 = require("./b"); +var b1 = require("./b"); +var x; +if (a_1["default"]) + x = a_1["default"]; +if (a_1.a1) + x = a_1.a1; +if (a_1.a1) + x = a_1.a1; +if (b0) + x = b0; +if (b1) + x = b1; diff --git a/tests/baselines/reference/narrowedImports.symbols b/tests/baselines/reference/narrowedImports.symbols new file mode 100644 index 00000000000..86dc798c837 --- /dev/null +++ b/tests/baselines/reference/narrowedImports.symbols @@ -0,0 +1,61 @@ +=== /x.ts === +import a0, { a1, a1 as a2 } from "./a"; +>a0 : Symbol(a0, Decl(x.ts, 0, 6)) +>a1 : Symbol(a1, Decl(x.ts, 0, 12)) +>a1 : Symbol(a2, Decl(x.ts, 0, 16)) +>a2 : Symbol(a2, Decl(x.ts, 0, 16)) + +import * as b0 from "./b"; +>b0 : Symbol(b0, Decl(x.ts, 1, 6)) + +import b1 = require("./b"); +>b1 : Symbol(b1, Decl(x.ts, 1, 26)) + +let x: number; +>x : Symbol(x, Decl(x.ts, 4, 3)) + +if (a0) x = a0; +>a0 : Symbol(a0, Decl(x.ts, 0, 6)) +>x : Symbol(x, Decl(x.ts, 4, 3)) +>a0 : Symbol(a0, Decl(x.ts, 0, 6)) + +if (a1) x = a1; +>a1 : Symbol(a1, Decl(x.ts, 0, 12)) +>x : Symbol(x, Decl(x.ts, 4, 3)) +>a1 : Symbol(a1, Decl(x.ts, 0, 12)) + +if (a2) x = a2; +>a2 : Symbol(a2, Decl(x.ts, 0, 16)) +>x : Symbol(x, Decl(x.ts, 4, 3)) +>a2 : Symbol(a2, Decl(x.ts, 0, 16)) + +if (b0) x = b0; +>b0 : Symbol(b0, Decl(x.ts, 1, 6)) +>x : Symbol(x, Decl(x.ts, 4, 3)) +>b0 : Symbol(b0, Decl(x.ts, 1, 6)) + +if (b1) x = b1; +>b1 : Symbol(b1, Decl(x.ts, 1, 26)) +>x : Symbol(x, Decl(x.ts, 4, 3)) +>b1 : Symbol(b1, Decl(x.ts, 1, 26)) + +=== /a.d.ts === +declare const a0: number | undefined; +>a0 : Symbol(a0, Decl(a.d.ts, 0, 13)) + +export default a0; +>a0 : Symbol(a0, Decl(a.d.ts, 0, 13)) + +export const a1: number | undefined; +>a1 : Symbol(a1, Decl(a.d.ts, 2, 12)) + +=== /b.d.ts === +declare const b: number | undefined; +>b : Symbol(b, Decl(b.d.ts, 0, 13), Decl(b.d.ts, 0, 36)) + +declare namespace b {} +>b : Symbol(b, Decl(b.d.ts, 0, 13), Decl(b.d.ts, 0, 36)) + +export = b; +>b : Symbol(b, Decl(b.d.ts, 0, 13), Decl(b.d.ts, 0, 36)) + diff --git a/tests/baselines/reference/narrowedImports.types b/tests/baselines/reference/narrowedImports.types new file mode 100644 index 00000000000..153e653046d --- /dev/null +++ b/tests/baselines/reference/narrowedImports.types @@ -0,0 +1,66 @@ +=== /x.ts === +import a0, { a1, a1 as a2 } from "./a"; +>a0 : number | undefined +>a1 : number | undefined +>a1 : number | undefined +>a2 : number | undefined + +import * as b0 from "./b"; +>b0 : number | undefined + +import b1 = require("./b"); +>b1 : number | undefined + +let x: number; +>x : number + +if (a0) x = a0; +>a0 : number | undefined +>x = a0 : number +>x : number +>a0 : number + +if (a1) x = a1; +>a1 : number | undefined +>x = a1 : number +>x : number +>a1 : number + +if (a2) x = a2; +>a2 : number | undefined +>x = a2 : number +>x : number +>a2 : number + +if (b0) x = b0; +>b0 : number | undefined +>x = b0 : number +>x : number +>b0 : number + +if (b1) x = b1; +>b1 : number | undefined +>x = b1 : number +>x : number +>b1 : number + +=== /a.d.ts === +declare const a0: number | undefined; +>a0 : number | undefined + +export default a0; +>a0 : number | undefined + +export const a1: number | undefined; +>a1 : number | undefined + +=== /b.d.ts === +declare const b: number | undefined; +>b : number | undefined + +declare namespace b {} +>b : number | undefined + +export = b; +>b : number | undefined + diff --git a/tests/baselines/reference/narrowedImports_assumeInitialized.js b/tests/baselines/reference/narrowedImports_assumeInitialized.js new file mode 100644 index 00000000000..7a45b05382a --- /dev/null +++ b/tests/baselines/reference/narrowedImports_assumeInitialized.js @@ -0,0 +1,18 @@ +//// [tests/cases/compiler/narrowedImports_assumeInitialized.ts] //// + +//// [a.d.ts] +declare namespace a { + export const x: number; +} +export = a; + +//// [b.ts] +import a = require("./a"); +a.x; + + +//// [b.js] +"use strict"; +exports.__esModule = true; +var a = require("./a"); +a.x; diff --git a/tests/baselines/reference/narrowedImports_assumeInitialized.symbols b/tests/baselines/reference/narrowedImports_assumeInitialized.symbols new file mode 100644 index 00000000000..e7f67d7dd33 --- /dev/null +++ b/tests/baselines/reference/narrowedImports_assumeInitialized.symbols @@ -0,0 +1,19 @@ +=== /b.ts === +import a = require("./a"); +>a : Symbol(a, Decl(b.ts, 0, 0)) + +a.x; +>a.x : Symbol(a.x, Decl(a.d.ts, 1, 16)) +>a : Symbol(a, Decl(b.ts, 0, 0)) +>x : Symbol(a.x, Decl(a.d.ts, 1, 16)) + +=== /a.d.ts === +declare namespace a { +>a : Symbol(a, Decl(a.d.ts, 0, 0)) + + export const x: number; +>x : Symbol(x, Decl(a.d.ts, 1, 16)) +} +export = a; +>a : Symbol(a, Decl(a.d.ts, 0, 0)) + diff --git a/tests/baselines/reference/narrowedImports_assumeInitialized.types b/tests/baselines/reference/narrowedImports_assumeInitialized.types new file mode 100644 index 00000000000..9f6b6fe8bbe --- /dev/null +++ b/tests/baselines/reference/narrowedImports_assumeInitialized.types @@ -0,0 +1,19 @@ +=== /b.ts === +import a = require("./a"); +>a : typeof a + +a.x; +>a.x : number +>a : typeof a +>x : number + +=== /a.d.ts === +declare namespace a { +>a : typeof a + + export const x: number; +>x : number +} +export = a; +>a : typeof a + diff --git a/tests/cases/compiler/narrowedImports.ts b/tests/cases/compiler/narrowedImports.ts new file mode 100644 index 00000000000..7049abfb4ff --- /dev/null +++ b/tests/cases/compiler/narrowedImports.ts @@ -0,0 +1,24 @@ +// @strictNullChecks: true + +// @Filename: /a.d.ts +declare const a0: number | undefined; +export default a0; +export const a1: number | undefined; + +// @Filename: /b.d.ts +declare const b: number | undefined; +declare namespace b {} +export = b; + +// @Filename: /x.ts +import a0, { a1, a1 as a2 } from "./a"; +import * as b0 from "./b"; +import b1 = require("./b"); + +let x: number; + +if (a0) x = a0; +if (a1) x = a1; +if (a2) x = a2; +if (b0) x = b0; +if (b1) x = b1; diff --git a/tests/cases/compiler/narrowedImports_assumeInitialized.ts b/tests/cases/compiler/narrowedImports_assumeInitialized.ts new file mode 100644 index 00000000000..ec651ba9ca6 --- /dev/null +++ b/tests/cases/compiler/narrowedImports_assumeInitialized.ts @@ -0,0 +1,11 @@ +// @strictNullChecks: true + +// @Filename: /a.d.ts +declare namespace a { + export const x: number; +} +export = a; + +// @Filename: /b.ts +import a = require("./a"); +a.x;