// These utilities are common to multiple language service features. /* @internal */ namespace ts { export interface ListItemInfo { listItemIndex: number; list: Node; } export function getLineStartPositionForPosition(position: number, sourceFile: SourceFile): number { const lineStarts = sourceFile.getLineStarts(); const line = sourceFile.getLineAndCharacterOfPosition(position).line; return lineStarts[line]; } export function rangeContainsRange(r1: TextRange, r2: TextRange): boolean { return startEndContainsRange(r1.pos, r1.end, r2); } export function startEndContainsRange(start: number, end: number, range: TextRange): boolean { return start <= range.pos && end >= range.end; } export function rangeContainsStartEnd(range: TextRange, start: number, end: number): boolean { return range.pos <= start && range.end >= end; } export function rangeOverlapsWithStartEnd(r1: TextRange, start: number, end: number) { return startEndOverlapsWithStartEnd(r1.pos, r1.end, start, end); } export function startEndOverlapsWithStartEnd(start1: number, end1: number, start2: number, end2: number) { const start = Math.max(start1, start2); const end = Math.min(end1, end2); return start < end; } export function positionBelongsToNode(candidate: Node, position: number, sourceFile: SourceFile): boolean { return candidate.end > position || !isCompletedNode(candidate, sourceFile); } export function isCompletedNode(n: Node, sourceFile: SourceFile): boolean { if (nodeIsMissing(n)) { return false; } switch (n.kind) { case SyntaxKind.ClassDeclaration: case SyntaxKind.InterfaceDeclaration: case SyntaxKind.EnumDeclaration: case SyntaxKind.ObjectLiteralExpression: case SyntaxKind.ObjectBindingPattern: case SyntaxKind.TypeLiteral: case SyntaxKind.Block: case SyntaxKind.ModuleBlock: case SyntaxKind.CaseBlock: case SyntaxKind.NamedImports: case SyntaxKind.NamedExports: return nodeEndsWith(n, SyntaxKind.CloseBraceToken, sourceFile); case SyntaxKind.CatchClause: return isCompletedNode((n).block, sourceFile); case SyntaxKind.NewExpression: if (!(n).arguments) { return true; } // fall through case SyntaxKind.CallExpression: case SyntaxKind.ParenthesizedExpression: case SyntaxKind.ParenthesizedType: return nodeEndsWith(n, SyntaxKind.CloseParenToken, sourceFile); case SyntaxKind.FunctionType: case SyntaxKind.ConstructorType: return isCompletedNode((n).type, sourceFile); case SyntaxKind.Constructor: case SyntaxKind.GetAccessor: case SyntaxKind.SetAccessor: case SyntaxKind.FunctionDeclaration: case SyntaxKind.FunctionExpression: case SyntaxKind.MethodDeclaration: case SyntaxKind.MethodSignature: case SyntaxKind.ConstructSignature: case SyntaxKind.CallSignature: case SyntaxKind.ArrowFunction: if ((n).body) { return isCompletedNode((n).body, sourceFile); } if ((n).type) { return isCompletedNode((n).type, sourceFile); } // Even though type parameters can be unclosed, we can get away with // having at least a closing paren. return hasChildOfKind(n, SyntaxKind.CloseParenToken, sourceFile); case SyntaxKind.ModuleDeclaration: return (n).body && isCompletedNode((n).body, sourceFile); case SyntaxKind.IfStatement: if ((n).elseStatement) { return isCompletedNode((n).elseStatement, sourceFile); } return isCompletedNode((n).thenStatement, sourceFile); case SyntaxKind.ExpressionStatement: return isCompletedNode((n).expression, sourceFile) || hasChildOfKind(n, SyntaxKind.SemicolonToken); case SyntaxKind.ArrayLiteralExpression: case SyntaxKind.ArrayBindingPattern: case SyntaxKind.ElementAccessExpression: case SyntaxKind.ComputedPropertyName: case SyntaxKind.TupleType: return nodeEndsWith(n, SyntaxKind.CloseBracketToken, sourceFile); case SyntaxKind.IndexSignature: if ((n).type) { return isCompletedNode((n).type, sourceFile); } return hasChildOfKind(n, SyntaxKind.CloseBracketToken, sourceFile); case SyntaxKind.CaseClause: case SyntaxKind.DefaultClause: // there is no such thing as terminator token for CaseClause/DefaultClause so for simplicity always consider them non-completed return false; case SyntaxKind.ForStatement: case SyntaxKind.ForInStatement: case SyntaxKind.ForOfStatement: case SyntaxKind.WhileStatement: return isCompletedNode((n).statement, sourceFile); case SyntaxKind.DoStatement: // rough approximation: if DoStatement has While keyword - then if node is completed is checking the presence of ')'; const hasWhileKeyword = findChildOfKind(n, SyntaxKind.WhileKeyword, sourceFile); if (hasWhileKeyword) { return nodeEndsWith(n, SyntaxKind.CloseParenToken, sourceFile); } return isCompletedNode((n).statement, sourceFile); case SyntaxKind.TypeQuery: return isCompletedNode((n).exprName, sourceFile); case SyntaxKind.TypeOfExpression: case SyntaxKind.DeleteExpression: case SyntaxKind.VoidExpression: case SyntaxKind.YieldExpression: case SyntaxKind.SpreadElementExpression: const unaryWordExpression = (n); return isCompletedNode(unaryWordExpression.expression, sourceFile); case SyntaxKind.TaggedTemplateExpression: return isCompletedNode((n).template, sourceFile); case SyntaxKind.TemplateExpression: const lastSpan = lastOrUndefined((n).templateSpans); return isCompletedNode(lastSpan, sourceFile); case SyntaxKind.TemplateSpan: return nodeIsPresent((n).literal); case SyntaxKind.ExportDeclaration: case SyntaxKind.ImportDeclaration: return nodeIsPresent((n).moduleSpecifier); case SyntaxKind.PrefixUnaryExpression: return isCompletedNode((n).operand, sourceFile); case SyntaxKind.BinaryExpression: return isCompletedNode((n).right, sourceFile); case SyntaxKind.ConditionalExpression: return isCompletedNode((n).whenFalse, sourceFile); default: return true; } } /* * Checks if node ends with 'expectedLastToken'. * If child at position 'length - 1' is 'SemicolonToken' it is skipped and 'expectedLastToken' is compared with child at position 'length - 2'. */ function nodeEndsWith(n: Node, expectedLastToken: SyntaxKind, sourceFile: SourceFile): boolean { const children = n.getChildren(sourceFile); if (children.length) { const last = lastOrUndefined(children); if (last.kind === expectedLastToken) { return true; } else if (last.kind === SyntaxKind.SemicolonToken && children.length !== 1) { return children[children.length - 2].kind === expectedLastToken; } } return false; } export function findListItemInfo(node: Node): ListItemInfo { const list = findContainingList(node); // It is possible at this point for syntaxList to be undefined, either if // node.parent had no list child, or if none of its list children contained // the span of node. If this happens, return undefined. The caller should // handle this case. if (!list) { return undefined; } const children = list.getChildren(); const listItemIndex = indexOf(children, node); return { listItemIndex, list }; } export function hasChildOfKind(n: Node, kind: SyntaxKind, sourceFile?: SourceFile): boolean { return !!findChildOfKind(n, kind, sourceFile); } export function findChildOfKind(n: Node, kind: SyntaxKind, sourceFile?: SourceFile): Node { return forEach(n.getChildren(sourceFile), c => c.kind === kind && c); } export function findContainingList(node: Node): Node { // The node might be a list element (nonsynthetic) or a comma (synthetic). Either way, it will // be parented by the container of the SyntaxList, not the SyntaxList itself. // In order to find the list item index, we first need to locate SyntaxList itself and then search // for the position of the relevant node (or comma). const syntaxList = forEach(node.parent.getChildren(), c => { // find syntax list that covers the span of the node if (c.kind === SyntaxKind.SyntaxList && c.pos <= node.pos && c.end >= node.end) { return c; } }); // Either we didn't find an appropriate list, or the list must contain us. Debug.assert(!syntaxList || contains(syntaxList.getChildren(), node)); return syntaxList; } /* Gets the token whose text has range [start, end) and * position >= start and (position < end or (position === end && token is keyword or identifier)) */ export function getTouchingWord(sourceFile: SourceFile, position: number, includeJsDocComment = false): Node { return getTouchingToken(sourceFile, position, n => isWord(n.kind), includeJsDocComment); } /* Gets the token whose text has range [start, end) and position >= start * and (position < end or (position === end && token is keyword or identifier or numeric/string literal)) */ export function getTouchingPropertyName(sourceFile: SourceFile, position: number, includeJsDocComment = false): Node { return getTouchingToken(sourceFile, position, n => isPropertyName(n.kind), includeJsDocComment); } /** Returns the token if position is in [start, end) or if position === end and includeItemAtEndPosition(token) === true */ export function getTouchingToken(sourceFile: SourceFile, position: number, includeItemAtEndPosition?: (n: Node) => boolean, includeJsDocComment = false): Node { return getTokenAtPositionWorker(sourceFile, position, /*allowPositionInLeadingTrivia*/ false, includeItemAtEndPosition, includeJsDocComment); } /** Returns a token if position is in [start-of-leading-trivia, end) */ export function getTokenAtPosition(sourceFile: SourceFile, position: number, includeJsDocComment = false): Node { return getTokenAtPositionWorker(sourceFile, position, /*allowPositionInLeadingTrivia*/ true, /*includeItemAtEndPosition*/ undefined, includeJsDocComment); } /** Get the token whose text contains the position */ function getTokenAtPositionWorker(sourceFile: SourceFile, position: number, allowPositionInLeadingTrivia: boolean, includeItemAtEndPosition: (n: Node) => boolean, includeJsDocComment = false): Node { let current: Node = sourceFile; outer: while (true) { if (isToken(current)) { // exit early return current; } if (includeJsDocComment) { const jsDocChildren = ts.filter(current.getChildren(), isJSDocNode); for (const jsDocChild of jsDocChildren) { const start = allowPositionInLeadingTrivia ? jsDocChild.getFullStart() : jsDocChild.getStart(sourceFile, includeJsDocComment); if (start <= position) { const end = jsDocChild.getEnd(); if (position < end || (position === end && jsDocChild.kind === SyntaxKind.EndOfFileToken)) { current = jsDocChild; continue outer; } else if (includeItemAtEndPosition && end === position) { const previousToken = findPrecedingToken(position, sourceFile, jsDocChild); if (previousToken && includeItemAtEndPosition(previousToken)) { return previousToken; } } } } } // find the child that contains 'position' for (let i = 0, n = current.getChildCount(sourceFile); i < n; i++) { const child = current.getChildAt(i); // all jsDocComment nodes were already visited if (isJSDocNode(child)) { continue; } const start = allowPositionInLeadingTrivia ? child.getFullStart() : child.getStart(sourceFile, includeJsDocComment); if (start <= position) { const end = child.getEnd(); if (position < end || (position === end && child.kind === SyntaxKind.EndOfFileToken)) { current = child; continue outer; } else if (includeItemAtEndPosition && end === position) { const previousToken = findPrecedingToken(position, sourceFile, child); if (previousToken && includeItemAtEndPosition(previousToken)) { return previousToken; } } } } return current; } } /** * The token on the left of the position is the token that strictly includes the position * or sits to the left of the cursor if it is on a boundary. For example * * fo|o -> will return foo * foo |bar -> will return foo * */ export function findTokenOnLeftOfPosition(file: SourceFile, position: number): Node { // Ideally, getTokenAtPosition should return a token. However, it is currently // broken, so we do a check to make sure the result was indeed a token. const tokenAtPosition = getTokenAtPosition(file, position); if (isToken(tokenAtPosition) && position > tokenAtPosition.getStart(file) && position < tokenAtPosition.getEnd()) { return tokenAtPosition; } return findPrecedingToken(position, file); } export function findNextToken(previousToken: Node, parent: Node): Node { return find(parent); function find(n: Node): Node { if (isToken(n) && n.pos === previousToken.end) { // this is token that starts at the end of previous token - return it return n; } const children = n.getChildren(); for (const child of children) { const shouldDiveInChildNode = // previous token is enclosed somewhere in the child (child.pos <= previousToken.pos && child.end > previousToken.end) || // previous token ends exactly at the beginning of child (child.pos === previousToken.end); if (shouldDiveInChildNode && nodeHasTokens(child)) { return find(child); } } return undefined; } } export function findPrecedingToken(position: number, sourceFile: SourceFile, startNode?: Node): Node { return find(startNode || sourceFile); function findRightmostToken(n: Node): Node { if (isToken(n) || n.kind === SyntaxKind.JsxText) { return n; } const children = n.getChildren(); const candidate = findRightmostChildNodeWithTokens(children, /*exclusiveStartPosition*/ children.length); return candidate && findRightmostToken(candidate); } function find(n: Node): Node { if (isToken(n) || n.kind === SyntaxKind.JsxText) { return n; } const children = n.getChildren(); for (let i = 0, len = children.length; i < len; i++) { const child = children[i]; // condition 'position < child.end' checks if child node end after the position // in the example below this condition will be false for 'aaaa' and 'bbbb' and true for 'ccc' // aaaa___bbbb___$__ccc // after we found child node with end after the position we check if start of the node is after the position. // if yes - then position is in the trivia and we need to look into the previous child to find the token in question. // if no - position is in the node itself so we should recurse in it. // NOTE: JsxText is a weird kind of node that can contain only whitespaces (since they are not counted as trivia). // if this is the case - then we should assume that token in question is located in previous child. if (position < child.end && (nodeHasTokens(child) || child.kind === SyntaxKind.JsxText)) { const start = child.getStart(sourceFile); const lookInPreviousChild = (start >= position) || // cursor in the leading trivia (child.kind === SyntaxKind.JsxText && start === child.end); // whitespace only JsxText if (lookInPreviousChild) { // actual start of the node is past the position - previous token should be at the end of previous child const candidate = findRightmostChildNodeWithTokens(children, /*exclusiveStartPosition*/ i); return candidate && findRightmostToken(candidate); } else { // candidate should be in this node return find(child); } } } Debug.assert(startNode !== undefined || n.kind === SyntaxKind.SourceFile); // Here we know that none of child token nodes embrace the position, // the only known case is when position is at the end of the file. // Try to find the rightmost token in the file without filtering. // Namely we are skipping the check: 'position < node.end' if (children.length) { const candidate = findRightmostChildNodeWithTokens(children, /*exclusiveStartPosition*/ children.length); return candidate && findRightmostToken(candidate); } } /// finds last node that is considered as candidate for search (isCandidate(node) === true) starting from 'exclusiveStartPosition' function findRightmostChildNodeWithTokens(children: Node[], exclusiveStartPosition: number): Node { for (let i = exclusiveStartPosition - 1; i >= 0; i--) { if (nodeHasTokens(children[i])) { return children[i]; } } } } export function isInString(sourceFile: SourceFile, position: number): boolean { const previousToken = findPrecedingToken(position, sourceFile); if (previousToken && (previousToken.kind === SyntaxKind.StringLiteral || previousToken.kind === SyntaxKind.StringLiteralType)) { const start = previousToken.getStart(); const end = previousToken.getEnd(); // To be "in" one of these literals, the position has to be: // 1. entirely within the token text. // 2. at the end position of an unterminated token. // 3. at the end of a regular expression (due to trailing flags like '/foo/g'). if (start < position && position < end) { return true; } if (position === end) { return !!(previousToken).isUnterminated; } } return false; } export function isInComment(sourceFile: SourceFile, position: number) { return isInCommentHelper(sourceFile, position, /*predicate*/ undefined); } /** * returns true if the position is in between the open and close elements of an JSX expression. */ export function isInsideJsxElementOrAttribute(sourceFile: SourceFile, position: number) { const token = getTokenAtPosition(sourceFile, position); if (!token) { return false; } if (token.kind === SyntaxKind.JsxText) { return true; } //
Hello |
if (token.kind === SyntaxKind.LessThanToken && token.parent.kind === SyntaxKind.JsxText) { return true; } //
{ |
or
if (token.kind === SyntaxKind.LessThanToken && token.parent.kind === SyntaxKind.JsxExpression) { return true; } //
{ // | // } < /div> if (token && token.kind === SyntaxKind.CloseBraceToken && token.parent.kind === SyntaxKind.JsxExpression) { return true; } //
|
if (token.kind === SyntaxKind.LessThanToken && token.parent.kind === SyntaxKind.JsxClosingElement) { return true; } return false; } export function isInTemplateString(sourceFile: SourceFile, position: number) { const token = getTokenAtPosition(sourceFile, position); return isTemplateLiteralKind(token.kind) && position > token.getStart(sourceFile); } /** * Returns true if the cursor at position in sourceFile is within a comment that additionally * satisfies predicate, and false otherwise. */ export function isInCommentHelper(sourceFile: SourceFile, position: number, predicate?: (c: CommentRange) => boolean): boolean { const token = getTokenAtPosition(sourceFile, position); if (token && position <= token.getStart(sourceFile)) { const commentRanges = getLeadingCommentRanges(sourceFile.text, token.pos); // The end marker of a single-line comment does not include the newline character. // In the following case, we are inside a comment (^ denotes the cursor position): // // // asdf ^\n // // But for multi-line comments, we don't want to be inside the comment in the following case: // // /* asdf */^ // // Internally, we represent the end of the comment at the newline and closing '/', respectively. return predicate ? forEach(commentRanges, c => c.pos < position && (c.kind == SyntaxKind.SingleLineCommentTrivia ? position <= c.end : position < c.end) && predicate(c)) : forEach(commentRanges, c => c.pos < position && (c.kind == SyntaxKind.SingleLineCommentTrivia ? position <= c.end : position < c.end)); } return false; } export function hasDocComment(sourceFile: SourceFile, position: number) { const token = getTokenAtPosition(sourceFile, position); // First, we have to see if this position actually landed in a comment. const commentRanges = getLeadingCommentRanges(sourceFile.text, token.pos); return forEach(commentRanges, jsDocPrefix); function jsDocPrefix(c: CommentRange): boolean { const text = sourceFile.text; return text.length >= c.pos + 3 && text[c.pos] === "/" && text[c.pos + 1] === "*" && text[c.pos + 2] === "*"; } } /** * Get the corresponding JSDocTag node if the position is in a jsDoc comment */ export function getJsDocTagAtPosition(sourceFile: SourceFile, position: number): JSDocTag { let node = ts.getTokenAtPosition(sourceFile, position); if (isToken(node)) { switch (node.kind) { case SyntaxKind.VarKeyword: case SyntaxKind.LetKeyword: case SyntaxKind.ConstKeyword: // if the current token is var, let or const, skip the VariableDeclarationList node = node.parent === undefined ? undefined : node.parent.parent; break; default: node = node.parent; break; } } if (node) { if (node.jsDocComments) { for (const jsDocComment of node.jsDocComments) { for (const tag of jsDocComment.tags) { if (tag.pos <= position && position <= tag.end) { return tag; } } } } } return undefined; } function nodeHasTokens(n: Node): boolean { // If we have a token or node that has a non-zero width, it must have tokens. // Note, that getWidth() does not take trivia into account. return n.getWidth() !== 0; } export function getNodeModifiers(node: Node): string { const flags = getCombinedNodeFlags(node); const result: string[] = []; if (flags & NodeFlags.Private) result.push(ScriptElementKindModifier.privateMemberModifier); if (flags & NodeFlags.Protected) result.push(ScriptElementKindModifier.protectedMemberModifier); if (flags & NodeFlags.Public) result.push(ScriptElementKindModifier.publicMemberModifier); if (flags & NodeFlags.Static) result.push(ScriptElementKindModifier.staticModifier); if (flags & NodeFlags.Abstract) result.push(ScriptElementKindModifier.abstractModifier); if (flags & NodeFlags.Export) result.push(ScriptElementKindModifier.exportedModifier); if (isInAmbientContext(node)) result.push(ScriptElementKindModifier.ambientModifier); return result.length > 0 ? result.join(",") : ScriptElementKindModifier.none; } export function getTypeArgumentOrTypeParameterList(node: Node): NodeArray { if (node.kind === SyntaxKind.TypeReference || node.kind === SyntaxKind.CallExpression) { return (node).typeArguments; } if (isFunctionLike(node) || node.kind === SyntaxKind.ClassDeclaration || node.kind === SyntaxKind.InterfaceDeclaration) { return (node).typeParameters; } return undefined; } export function isToken(n: Node): boolean { return n.kind >= SyntaxKind.FirstToken && n.kind <= SyntaxKind.LastToken; } export function isWord(kind: SyntaxKind): boolean { return kind === SyntaxKind.Identifier || isKeyword(kind); } function isPropertyName(kind: SyntaxKind): boolean { return kind === SyntaxKind.StringLiteral || kind === SyntaxKind.NumericLiteral || isWord(kind); } export function isComment(kind: SyntaxKind): boolean { return kind === SyntaxKind.SingleLineCommentTrivia || kind === SyntaxKind.MultiLineCommentTrivia; } export function isStringOrRegularExpressionOrTemplateLiteral(kind: SyntaxKind): boolean { if (kind === SyntaxKind.StringLiteral || kind === SyntaxKind.StringLiteralType || kind === SyntaxKind.RegularExpressionLiteral || isTemplateLiteralKind(kind)) { return true; } return false; } export function isPunctuation(kind: SyntaxKind): boolean { return SyntaxKind.FirstPunctuation <= kind && kind <= SyntaxKind.LastPunctuation; } export function isInsideTemplateLiteral(node: LiteralExpression, position: number) { return isTemplateLiteralKind(node.kind) && (node.getStart() < position && position < node.getEnd()) || (!!node.isUnterminated && position === node.getEnd()); } export function isAccessibilityModifier(kind: SyntaxKind) { switch (kind) { case SyntaxKind.PublicKeyword: case SyntaxKind.PrivateKeyword: case SyntaxKind.ProtectedKeyword: return true; } return false; } export function compareDataObjects(dst: any, src: any): boolean { for (const e in dst) { if (typeof dst[e] === "object") { if (!compareDataObjects(dst[e], src[e])) { return false; } } else if (typeof dst[e] !== "function") { if (dst[e] !== src[e]) { return false; } } } return true; } export function isArrayLiteralOrObjectLiteralDestructuringPattern(node: Node) { if (node.kind === SyntaxKind.ArrayLiteralExpression || node.kind === SyntaxKind.ObjectLiteralExpression) { // [a,b,c] from: // [a, b, c] = someExpression; if (node.parent.kind === SyntaxKind.BinaryExpression && (node.parent).left === node && (node.parent).operatorToken.kind === SyntaxKind.EqualsToken) { return true; } // [a, b, c] from: // for([a, b, c] of expression) if (node.parent.kind === SyntaxKind.ForOfStatement && (node.parent).initializer === node) { return true; } // [a, b, c] of // [x, [a, b, c] ] = someExpression // or // {x, a: {a, b, c} } = someExpression if (isArrayLiteralOrObjectLiteralDestructuringPattern(node.parent.kind === SyntaxKind.PropertyAssignment ? node.parent.parent : node.parent)) { return true; } } return false; } } // Display-part writer helpers /* @internal */ namespace ts { export function isFirstDeclarationOfSymbolParameter(symbol: Symbol) { return symbol.declarations && symbol.declarations.length > 0 && symbol.declarations[0].kind === SyntaxKind.Parameter; } const displayPartWriter = getDisplayPartWriter(); function getDisplayPartWriter(): DisplayPartsSymbolWriter { let displayParts: SymbolDisplayPart[]; let lineStart: boolean; let indent: number; resetWriter(); return { displayParts: () => displayParts, writeKeyword: text => writeKind(text, SymbolDisplayPartKind.keyword), writeOperator: text => writeKind(text, SymbolDisplayPartKind.operator), writePunctuation: text => writeKind(text, SymbolDisplayPartKind.punctuation), writeSpace: text => writeKind(text, SymbolDisplayPartKind.space), writeStringLiteral: text => writeKind(text, SymbolDisplayPartKind.stringLiteral), writeParameter: text => writeKind(text, SymbolDisplayPartKind.parameterName), writeSymbol, writeLine, increaseIndent: () => { indent++; }, decreaseIndent: () => { indent--; }, clear: resetWriter, trackSymbol: () => { }, reportInaccessibleThisError: () => { } }; function writeIndent() { if (lineStart) { const indentString = getIndentString(indent); if (indentString) { displayParts.push(displayPart(indentString, SymbolDisplayPartKind.space)); } lineStart = false; } } function writeKind(text: string, kind: SymbolDisplayPartKind) { writeIndent(); displayParts.push(displayPart(text, kind)); } function writeSymbol(text: string, symbol: Symbol) { writeIndent(); displayParts.push(symbolPart(text, symbol)); } function writeLine() { displayParts.push(lineBreakPart()); lineStart = true; } function resetWriter() { displayParts = []; lineStart = true; indent = 0; } } export function symbolPart(text: string, symbol: Symbol) { return displayPart(text, displayPartKind(symbol), symbol); function displayPartKind(symbol: Symbol): SymbolDisplayPartKind { const flags = symbol.flags; if (flags & SymbolFlags.Variable) { return isFirstDeclarationOfSymbolParameter(symbol) ? SymbolDisplayPartKind.parameterName : SymbolDisplayPartKind.localName; } else if (flags & SymbolFlags.Property) { return SymbolDisplayPartKind.propertyName; } else if (flags & SymbolFlags.GetAccessor) { return SymbolDisplayPartKind.propertyName; } else if (flags & SymbolFlags.SetAccessor) { return SymbolDisplayPartKind.propertyName; } else if (flags & SymbolFlags.EnumMember) { return SymbolDisplayPartKind.enumMemberName; } else if (flags & SymbolFlags.Function) { return SymbolDisplayPartKind.functionName; } else if (flags & SymbolFlags.Class) { return SymbolDisplayPartKind.className; } else if (flags & SymbolFlags.Interface) { return SymbolDisplayPartKind.interfaceName; } else if (flags & SymbolFlags.Enum) { return SymbolDisplayPartKind.enumName; } else if (flags & SymbolFlags.Module) { return SymbolDisplayPartKind.moduleName; } else if (flags & SymbolFlags.Method) { return SymbolDisplayPartKind.methodName; } else if (flags & SymbolFlags.TypeParameter) { return SymbolDisplayPartKind.typeParameterName; } else if (flags & SymbolFlags.TypeAlias) { return SymbolDisplayPartKind.aliasName; } else if (flags & SymbolFlags.Alias) { return SymbolDisplayPartKind.aliasName; } return SymbolDisplayPartKind.text; } } export function displayPart(text: string, kind: SymbolDisplayPartKind, symbol?: Symbol): SymbolDisplayPart { return { text: text, kind: SymbolDisplayPartKind[kind] }; } export function spacePart() { return displayPart(" ", SymbolDisplayPartKind.space); } export function keywordPart(kind: SyntaxKind) { return displayPart(tokenToString(kind), SymbolDisplayPartKind.keyword); } export function punctuationPart(kind: SyntaxKind) { return displayPart(tokenToString(kind), SymbolDisplayPartKind.punctuation); } export function operatorPart(kind: SyntaxKind) { return displayPart(tokenToString(kind), SymbolDisplayPartKind.operator); } export function textOrKeywordPart(text: string) { const kind = stringToToken(text); return kind === undefined ? textPart(text) : keywordPart(kind); } export function textPart(text: string) { return displayPart(text, SymbolDisplayPartKind.text); } const carriageReturnLineFeed = "\r\n"; /** * The default is CRLF. */ export function getNewLineOrDefaultFromHost(host: LanguageServiceHost | LanguageServiceShimHost) { return host.getNewLine ? host.getNewLine() : carriageReturnLineFeed; } export function lineBreakPart() { return displayPart("\n", SymbolDisplayPartKind.lineBreak); } export function mapToDisplayParts(writeDisplayParts: (writer: DisplayPartsSymbolWriter) => void): SymbolDisplayPart[] { writeDisplayParts(displayPartWriter); const result = displayPartWriter.displayParts(); displayPartWriter.clear(); return result; } export function typeToDisplayParts(typechecker: TypeChecker, type: Type, enclosingDeclaration?: Node, flags?: TypeFormatFlags): SymbolDisplayPart[] { return mapToDisplayParts(writer => { typechecker.getSymbolDisplayBuilder().buildTypeDisplay(type, writer, enclosingDeclaration, flags); }); } export function symbolToDisplayParts(typeChecker: TypeChecker, symbol: Symbol, enclosingDeclaration?: Node, meaning?: SymbolFlags, flags?: SymbolFormatFlags): SymbolDisplayPart[] { return mapToDisplayParts(writer => { typeChecker.getSymbolDisplayBuilder().buildSymbolDisplay(symbol, writer, enclosingDeclaration, meaning, flags); }); } export function signatureToDisplayParts(typechecker: TypeChecker, signature: Signature, enclosingDeclaration?: Node, flags?: TypeFormatFlags): SymbolDisplayPart[] { return mapToDisplayParts(writer => { typechecker.getSymbolDisplayBuilder().buildSignatureDisplay(signature, writer, enclosingDeclaration, flags); }); } export function getDeclaredName(typeChecker: TypeChecker, symbol: Symbol, location: Node): string { // If this is an export or import specifier it could have been renamed using the 'as' syntax. // If so we want to search for whatever is under the cursor. if (isImportOrExportSpecifierName(location)) { return location.getText(); } else if (isStringOrNumericLiteral(location.kind) && location.parent.kind === SyntaxKind.ComputedPropertyName) { return (location).text; } // Try to get the local symbol if we're dealing with an 'export default' // since that symbol has the "true" name. const localExportDefaultSymbol = getLocalSymbolForExportDefault(symbol); const name = typeChecker.symbolToString(localExportDefaultSymbol || symbol); return name; } export function isImportOrExportSpecifierName(location: Node): boolean { return location.parent && (location.parent.kind === SyntaxKind.ImportSpecifier || location.parent.kind === SyntaxKind.ExportSpecifier) && (location.parent).propertyName === location; } /** * Strip off existed single quotes or double quotes from a given string * * @return non-quoted string */ export function stripQuotes(name: string) { const length = name.length; if (length >= 2 && name.charCodeAt(0) === name.charCodeAt(length - 1) && (name.charCodeAt(0) === CharacterCodes.doubleQuote || name.charCodeAt(0) === CharacterCodes.singleQuote)) { return name.substring(1, length - 1); }; return name; } export function scriptKindIs(fileName: string, host: LanguageServiceHost, ...scriptKinds: ScriptKind[]): boolean { const scriptKind = getScriptKind(fileName, host); return forEach(scriptKinds, k => k === scriptKind); } export function getScriptKind(fileName: string, host?: LanguageServiceHost): ScriptKind { // First check to see if the script kind was specified by the host. Chances are the host // may override the default script kind for the file extension. let scriptKind: ScriptKind; if (host && host.getScriptKind) { scriptKind = host.getScriptKind(fileName); } if (!scriptKind || scriptKind === ScriptKind.Unknown) { scriptKind = getScriptKindFromFileName(fileName); } return ensureScriptKind(fileName, scriptKind); } }