merge with origin/master

This commit is contained in:
Vladimir Matveev
2016-09-27 10:22:41 -07:00
4908 changed files with 165378 additions and 93228 deletions
+5 -5
View File
@@ -56,7 +56,7 @@ namespace ts.BreakpointResolver {
return spanInNode(otherwiseOnNode);
}
function spanInNodeArray<T>(nodeArray: NodeArray<T>) {
function spanInNodeArray<T extends Node>(nodeArray: NodeArray<T>) {
return createTextSpanFromBounds(skipTrivia(sourceFile.text, nodeArray.pos), nodeArray.end);
}
@@ -296,7 +296,7 @@ namespace ts.BreakpointResolver {
}
}
if (isExpression(node)) {
if (isPartOfExpression(node)) {
switch (node.parent.kind) {
case SyntaxKind.DoStatement:
// Set span as if on while keyword
@@ -395,7 +395,7 @@ namespace ts.BreakpointResolver {
// Breakpoint is possible in variableDeclaration only if there is initialization
// or its declaration from 'for of'
if (variableDeclaration.initializer ||
(variableDeclaration.flags & NodeFlags.Export) ||
hasModifier(variableDeclaration, ModifierFlags.Export) ||
variableDeclaration.parent.parent.kind === SyntaxKind.ForOfStatement) {
return textSpanFromVariableDeclaration(variableDeclaration);
}
@@ -413,7 +413,7 @@ namespace ts.BreakpointResolver {
function canHaveSpanInParameterDeclaration(parameter: ParameterDeclaration): boolean {
// Breakpoint is possible on parameter only if it has initializer, is a rest parameter, or has public or private modifier
return !!parameter.initializer || parameter.dotDotDotToken !== undefined ||
!!(parameter.flags & NodeFlags.Public) || !!(parameter.flags & NodeFlags.Private);
hasModifier(parameter, ModifierFlags.Public | ModifierFlags.Private);
}
function spanInParameterDeclaration(parameter: ParameterDeclaration): TextSpan {
@@ -439,7 +439,7 @@ namespace ts.BreakpointResolver {
}
function canFunctionHaveSpanInWholeDeclaration(functionDeclaration: FunctionLikeDeclaration) {
return !!(functionDeclaration.flags & NodeFlags.Export) ||
return hasModifier(functionDeclaration, ModifierFlags.Export) ||
(functionDeclaration.parent.kind === SyntaxKind.ClassDeclaration && functionDeclaration.kind !== SyntaxKind.Constructor);
}
+987
View File
@@ -0,0 +1,987 @@
namespace ts {
/// Classifier
export function createClassifier(): Classifier {
const scanner = createScanner(ScriptTarget.Latest, /*skipTrivia*/ false);
/// We do not have a full parser support to know when we should parse a regex or not
/// If we consider every slash token to be a regex, we could be missing cases like "1/2/3", where
/// we have a series of divide operator. this list allows us to be more accurate by ruling out
/// locations where a regexp cannot exist.
const noRegexTable: boolean[] = [];
noRegexTable[SyntaxKind.Identifier] = true;
noRegexTable[SyntaxKind.StringLiteral] = true;
noRegexTable[SyntaxKind.NumericLiteral] = true;
noRegexTable[SyntaxKind.RegularExpressionLiteral] = true;
noRegexTable[SyntaxKind.ThisKeyword] = true;
noRegexTable[SyntaxKind.PlusPlusToken] = true;
noRegexTable[SyntaxKind.MinusMinusToken] = true;
noRegexTable[SyntaxKind.CloseParenToken] = true;
noRegexTable[SyntaxKind.CloseBracketToken] = true;
noRegexTable[SyntaxKind.CloseBraceToken] = true;
noRegexTable[SyntaxKind.TrueKeyword] = true;
noRegexTable[SyntaxKind.FalseKeyword] = true;
// Just a stack of TemplateHeads and OpenCurlyBraces, used to perform rudimentary (inexact)
// classification on template strings. Because of the context free nature of templates,
// the only precise way to classify a template portion would be by propagating the stack across
// lines, just as we do with the end-of-line state. However, this is a burden for implementers,
// and the behavior is entirely subsumed by the syntactic classifier anyway, so we instead
// flatten any nesting when the template stack is non-empty and encode it in the end-of-line state.
// Situations in which this fails are
// 1) When template strings are nested across different lines:
// `hello ${ `world
// ` }`
//
// Where on the second line, you will get the closing of a template,
// a closing curly, and a new template.
//
// 2) When substitution expressions have curly braces and the curly brace falls on the next line:
// `hello ${ () => {
// return "world" } } `
//
// Where on the second line, you will get the 'return' keyword,
// a string literal, and a template end consisting of '} } `'.
const templateStack: SyntaxKind[] = [];
/** Returns true if 'keyword2' can legally follow 'keyword1' in any language construct. */
function canFollow(keyword1: SyntaxKind, keyword2: SyntaxKind) {
if (isAccessibilityModifier(keyword1)) {
if (keyword2 === SyntaxKind.GetKeyword ||
keyword2 === SyntaxKind.SetKeyword ||
keyword2 === SyntaxKind.ConstructorKeyword ||
keyword2 === SyntaxKind.StaticKeyword) {
// Allow things like "public get", "public constructor" and "public static".
// These are all legal.
return true;
}
// Any other keyword following "public" is actually an identifier an not a real
// keyword.
return false;
}
// Assume any other keyword combination is legal. This can be refined in the future
// if there are more cases we want the classifier to be better at.
return true;
}
function convertClassifications(classifications: Classifications, text: string): ClassificationResult {
const entries: ClassificationInfo[] = [];
const dense = classifications.spans;
let lastEnd = 0;
for (let i = 0, n = dense.length; i < n; i += 3) {
const start = dense[i];
const length = dense[i + 1];
const type = <ClassificationType>dense[i + 2];
// Make a whitespace entry between the last item and this one.
if (lastEnd >= 0) {
const whitespaceLength = start - lastEnd;
if (whitespaceLength > 0) {
entries.push({ length: whitespaceLength, classification: TokenClass.Whitespace });
}
}
entries.push({ length, classification: convertClassification(type) });
lastEnd = start + length;
}
const whitespaceLength = text.length - lastEnd;
if (whitespaceLength > 0) {
entries.push({ length: whitespaceLength, classification: TokenClass.Whitespace });
}
return { entries, finalLexState: classifications.endOfLineState };
}
function convertClassification(type: ClassificationType): TokenClass {
switch (type) {
case ClassificationType.comment: return TokenClass.Comment;
case ClassificationType.keyword: return TokenClass.Keyword;
case ClassificationType.numericLiteral: return TokenClass.NumberLiteral;
case ClassificationType.operator: return TokenClass.Operator;
case ClassificationType.stringLiteral: return TokenClass.StringLiteral;
case ClassificationType.whiteSpace: return TokenClass.Whitespace;
case ClassificationType.punctuation: return TokenClass.Punctuation;
case ClassificationType.identifier:
case ClassificationType.className:
case ClassificationType.enumName:
case ClassificationType.interfaceName:
case ClassificationType.moduleName:
case ClassificationType.typeParameterName:
case ClassificationType.typeAliasName:
case ClassificationType.text:
case ClassificationType.parameterName:
default:
return TokenClass.Identifier;
}
}
function getClassificationsForLine(text: string, lexState: EndOfLineState, syntacticClassifierAbsent: boolean): ClassificationResult {
return convertClassifications(getEncodedLexicalClassifications(text, lexState, syntacticClassifierAbsent), text);
}
// If there is a syntactic classifier ('syntacticClassifierAbsent' is false),
// we will be more conservative in order to avoid conflicting with the syntactic classifier.
function getEncodedLexicalClassifications(text: string, lexState: EndOfLineState, syntacticClassifierAbsent: boolean): Classifications {
let offset = 0;
let token = SyntaxKind.Unknown;
let lastNonTriviaToken = SyntaxKind.Unknown;
// Empty out the template stack for reuse.
while (templateStack.length > 0) {
templateStack.pop();
}
// If we're in a string literal, then prepend: "\
// (and a newline). That way when we lex we'll think we're still in a string literal.
//
// If we're in a multiline comment, then prepend: /*
// (and a newline). That way when we lex we'll think we're still in a multiline comment.
switch (lexState) {
case EndOfLineState.InDoubleQuoteStringLiteral:
text = "\"\\\n" + text;
offset = 3;
break;
case EndOfLineState.InSingleQuoteStringLiteral:
text = "'\\\n" + text;
offset = 3;
break;
case EndOfLineState.InMultiLineCommentTrivia:
text = "/*\n" + text;
offset = 3;
break;
case EndOfLineState.InTemplateHeadOrNoSubstitutionTemplate:
text = "`\n" + text;
offset = 2;
break;
case EndOfLineState.InTemplateMiddleOrTail:
text = "}\n" + text;
offset = 2;
// fallthrough
case EndOfLineState.InTemplateSubstitutionPosition:
templateStack.push(SyntaxKind.TemplateHead);
break;
}
scanner.setText(text);
const result: Classifications = {
endOfLineState: EndOfLineState.None,
spans: []
};
// We can run into an unfortunate interaction between the lexical and syntactic classifier
// when the user is typing something generic. Consider the case where the user types:
//
// Foo<number
//
// From the lexical classifier's perspective, 'number' is a keyword, and so the word will
// be classified as such. However, from the syntactic classifier's tree-based perspective
// this is simply an expression with the identifier 'number' on the RHS of the less than
// token. So the classification will go back to being an identifier. The moment the user
// types again, number will become a keyword, then an identifier, etc. etc.
//
// To try to avoid this problem, we avoid classifying contextual keywords as keywords
// when the user is potentially typing something generic. We just can't do a good enough
// job at the lexical level, and so well leave it up to the syntactic classifier to make
// the determination.
//
// In order to determine if the user is potentially typing something generic, we use a
// weak heuristic where we track < and > tokens. It's a weak heuristic, but should
// work well enough in practice.
let angleBracketStack = 0;
do {
token = scanner.scan();
if (!isTrivia(token)) {
if ((token === SyntaxKind.SlashToken || token === SyntaxKind.SlashEqualsToken) && !noRegexTable[lastNonTriviaToken]) {
if (scanner.reScanSlashToken() === SyntaxKind.RegularExpressionLiteral) {
token = SyntaxKind.RegularExpressionLiteral;
}
}
else if (lastNonTriviaToken === SyntaxKind.DotToken && isKeyword(token)) {
token = SyntaxKind.Identifier;
}
else if (isKeyword(lastNonTriviaToken) && isKeyword(token) && !canFollow(lastNonTriviaToken, token)) {
// We have two keywords in a row. Only treat the second as a keyword if
// it's a sequence that could legally occur in the language. Otherwise
// treat it as an identifier. This way, if someone writes "private var"
// we recognize that 'var' is actually an identifier here.
token = SyntaxKind.Identifier;
}
else if (lastNonTriviaToken === SyntaxKind.Identifier &&
token === SyntaxKind.LessThanToken) {
// Could be the start of something generic. Keep track of that by bumping
// up the current count of generic contexts we may be in.
angleBracketStack++;
}
else if (token === SyntaxKind.GreaterThanToken && angleBracketStack > 0) {
// If we think we're currently in something generic, then mark that that
// generic entity is complete.
angleBracketStack--;
}
else if (token === SyntaxKind.AnyKeyword ||
token === SyntaxKind.StringKeyword ||
token === SyntaxKind.NumberKeyword ||
token === SyntaxKind.BooleanKeyword ||
token === SyntaxKind.SymbolKeyword) {
if (angleBracketStack > 0 && !syntacticClassifierAbsent) {
// If it looks like we're could be in something generic, don't classify this
// as a keyword. We may just get overwritten by the syntactic classifier,
// causing a noisy experience for the user.
token = SyntaxKind.Identifier;
}
}
else if (token === SyntaxKind.TemplateHead) {
templateStack.push(token);
}
else if (token === SyntaxKind.OpenBraceToken) {
// If we don't have anything on the template stack,
// then we aren't trying to keep track of a previously scanned template head.
if (templateStack.length > 0) {
templateStack.push(token);
}
}
else if (token === SyntaxKind.CloseBraceToken) {
// If we don't have anything on the template stack,
// then we aren't trying to keep track of a previously scanned template head.
if (templateStack.length > 0) {
const lastTemplateStackToken = lastOrUndefined(templateStack);
if (lastTemplateStackToken === SyntaxKind.TemplateHead) {
token = scanner.reScanTemplateToken();
// Only pop on a TemplateTail; a TemplateMiddle indicates there is more for us.
if (token === SyntaxKind.TemplateTail) {
templateStack.pop();
}
else {
Debug.assert(token === SyntaxKind.TemplateMiddle, "Should have been a template middle. Was " + token);
}
}
else {
Debug.assert(lastTemplateStackToken === SyntaxKind.OpenBraceToken, "Should have been an open brace. Was: " + token);
templateStack.pop();
}
}
}
lastNonTriviaToken = token;
}
processToken();
}
while (token !== SyntaxKind.EndOfFileToken);
return result;
function processToken(): void {
const start = scanner.getTokenPos();
const end = scanner.getTextPos();
addResult(start, end, classFromKind(token));
if (end >= text.length) {
if (token === SyntaxKind.StringLiteral) {
// Check to see if we finished up on a multiline string literal.
const tokenText = scanner.getTokenText();
if (scanner.isUnterminated()) {
const lastCharIndex = tokenText.length - 1;
let numBackslashes = 0;
while (tokenText.charCodeAt(lastCharIndex - numBackslashes) === CharacterCodes.backslash) {
numBackslashes++;
}
// If we have an odd number of backslashes, then the multiline string is unclosed
if (numBackslashes & 1) {
const quoteChar = tokenText.charCodeAt(0);
result.endOfLineState = quoteChar === CharacterCodes.doubleQuote
? EndOfLineState.InDoubleQuoteStringLiteral
: EndOfLineState.InSingleQuoteStringLiteral;
}
}
}
else if (token === SyntaxKind.MultiLineCommentTrivia) {
// Check to see if the multiline comment was unclosed.
if (scanner.isUnterminated()) {
result.endOfLineState = EndOfLineState.InMultiLineCommentTrivia;
}
}
else if (isTemplateLiteralKind(token)) {
if (scanner.isUnterminated()) {
if (token === SyntaxKind.TemplateTail) {
result.endOfLineState = EndOfLineState.InTemplateMiddleOrTail;
}
else if (token === SyntaxKind.NoSubstitutionTemplateLiteral) {
result.endOfLineState = EndOfLineState.InTemplateHeadOrNoSubstitutionTemplate;
}
else {
Debug.fail("Only 'NoSubstitutionTemplateLiteral's and 'TemplateTail's can be unterminated; got SyntaxKind #" + token);
}
}
}
else if (templateStack.length > 0 && lastOrUndefined(templateStack) === SyntaxKind.TemplateHead) {
result.endOfLineState = EndOfLineState.InTemplateSubstitutionPosition;
}
}
}
function addResult(start: number, end: number, classification: ClassificationType): void {
if (classification === ClassificationType.whiteSpace) {
// Don't bother with whitespace classifications. They're not needed.
return;
}
if (start === 0 && offset > 0) {
// We're classifying the first token, and this was a case where we prepended
// text. We should consider the start of this token to be at the start of
// the original text.
start += offset;
}
// All our tokens are in relation to the augmented text. Move them back to be
// relative to the original text.
start -= offset;
end -= offset;
const length = end - start;
if (length > 0) {
result.spans.push(start);
result.spans.push(length);
result.spans.push(classification);
}
}
}
function isBinaryExpressionOperatorToken(token: SyntaxKind): boolean {
switch (token) {
case SyntaxKind.AsteriskToken:
case SyntaxKind.SlashToken:
case SyntaxKind.PercentToken:
case SyntaxKind.PlusToken:
case SyntaxKind.MinusToken:
case SyntaxKind.LessThanLessThanToken:
case SyntaxKind.GreaterThanGreaterThanToken:
case SyntaxKind.GreaterThanGreaterThanGreaterThanToken:
case SyntaxKind.LessThanToken:
case SyntaxKind.GreaterThanToken:
case SyntaxKind.LessThanEqualsToken:
case SyntaxKind.GreaterThanEqualsToken:
case SyntaxKind.InstanceOfKeyword:
case SyntaxKind.InKeyword:
case SyntaxKind.AsKeyword:
case SyntaxKind.EqualsEqualsToken:
case SyntaxKind.ExclamationEqualsToken:
case SyntaxKind.EqualsEqualsEqualsToken:
case SyntaxKind.ExclamationEqualsEqualsToken:
case SyntaxKind.AmpersandToken:
case SyntaxKind.CaretToken:
case SyntaxKind.BarToken:
case SyntaxKind.AmpersandAmpersandToken:
case SyntaxKind.BarBarToken:
case SyntaxKind.BarEqualsToken:
case SyntaxKind.AmpersandEqualsToken:
case SyntaxKind.CaretEqualsToken:
case SyntaxKind.LessThanLessThanEqualsToken:
case SyntaxKind.GreaterThanGreaterThanEqualsToken:
case SyntaxKind.GreaterThanGreaterThanGreaterThanEqualsToken:
case SyntaxKind.PlusEqualsToken:
case SyntaxKind.MinusEqualsToken:
case SyntaxKind.AsteriskEqualsToken:
case SyntaxKind.SlashEqualsToken:
case SyntaxKind.PercentEqualsToken:
case SyntaxKind.EqualsToken:
case SyntaxKind.CommaToken:
return true;
default:
return false;
}
}
function isPrefixUnaryExpressionOperatorToken(token: SyntaxKind): boolean {
switch (token) {
case SyntaxKind.PlusToken:
case SyntaxKind.MinusToken:
case SyntaxKind.TildeToken:
case SyntaxKind.ExclamationToken:
case SyntaxKind.PlusPlusToken:
case SyntaxKind.MinusMinusToken:
return true;
default:
return false;
}
}
function isKeyword(token: SyntaxKind): boolean {
return token >= SyntaxKind.FirstKeyword && token <= SyntaxKind.LastKeyword;
}
function classFromKind(token: SyntaxKind): ClassificationType {
if (isKeyword(token)) {
return ClassificationType.keyword;
}
else if (isBinaryExpressionOperatorToken(token) || isPrefixUnaryExpressionOperatorToken(token)) {
return ClassificationType.operator;
}
else if (token >= SyntaxKind.FirstPunctuation && token <= SyntaxKind.LastPunctuation) {
return ClassificationType.punctuation;
}
switch (token) {
case SyntaxKind.NumericLiteral:
return ClassificationType.numericLiteral;
case SyntaxKind.StringLiteral:
return ClassificationType.stringLiteral;
case SyntaxKind.RegularExpressionLiteral:
return ClassificationType.regularExpressionLiteral;
case SyntaxKind.ConflictMarkerTrivia:
case SyntaxKind.MultiLineCommentTrivia:
case SyntaxKind.SingleLineCommentTrivia:
return ClassificationType.comment;
case SyntaxKind.WhitespaceTrivia:
case SyntaxKind.NewLineTrivia:
return ClassificationType.whiteSpace;
case SyntaxKind.Identifier:
default:
if (isTemplateLiteralKind(token)) {
return ClassificationType.stringLiteral;
}
return ClassificationType.identifier;
}
}
return {
getClassificationsForLine,
getEncodedLexicalClassifications
};
}
/* @internal */
export function getSemanticClassifications(typeChecker: TypeChecker, cancellationToken: CancellationToken, sourceFile: SourceFile, classifiableNames: Map<string>, span: TextSpan): ClassifiedSpan[] {
return convertClassifications(getEncodedSemanticClassifications(typeChecker, cancellationToken, sourceFile, classifiableNames, span));
}
function checkForClassificationCancellation(cancellationToken: CancellationToken, kind: SyntaxKind) {
// We don't want to actually call back into our host on every node to find out if we've
// been canceled. That would be an enormous amount of chattyness, along with the all
// the overhead of marshalling the data to/from the host. So instead we pick a few
// reasonable node kinds to bother checking on. These node kinds represent high level
// constructs that we would expect to see commonly, but just at a far less frequent
// interval.
//
// For example, in checker.ts (around 750k) we only have around 600 of these constructs.
// That means we're calling back into the host around every 1.2k of the file we process.
// Lib.d.ts has similar numbers.
switch (kind) {
case SyntaxKind.ModuleDeclaration:
case SyntaxKind.ClassDeclaration:
case SyntaxKind.InterfaceDeclaration:
case SyntaxKind.FunctionDeclaration:
cancellationToken.throwIfCancellationRequested();
}
}
/* @internal */
export function getEncodedSemanticClassifications(typeChecker: TypeChecker, cancellationToken: CancellationToken, sourceFile: SourceFile, classifiableNames: Map<string>, span: TextSpan): Classifications {
const result: number[] = [];
processNode(sourceFile);
return { spans: result, endOfLineState: EndOfLineState.None };
function pushClassification(start: number, length: number, type: ClassificationType) {
result.push(start);
result.push(length);
result.push(type);
}
function classifySymbol(symbol: Symbol, meaningAtPosition: SemanticMeaning): ClassificationType {
const flags = symbol.getFlags();
if ((flags & SymbolFlags.Classifiable) === SymbolFlags.None) {
return;
}
if (flags & SymbolFlags.Class) {
return ClassificationType.className;
}
else if (flags & SymbolFlags.Enum) {
return ClassificationType.enumName;
}
else if (flags & SymbolFlags.TypeAlias) {
return ClassificationType.typeAliasName;
}
else if (meaningAtPosition & SemanticMeaning.Type) {
if (flags & SymbolFlags.Interface) {
return ClassificationType.interfaceName;
}
else if (flags & SymbolFlags.TypeParameter) {
return ClassificationType.typeParameterName;
}
}
else if (flags & SymbolFlags.Module) {
// Only classify a module as such if
// - It appears in a namespace context.
// - There exists a module declaration which actually impacts the value side.
if (meaningAtPosition & SemanticMeaning.Namespace ||
(meaningAtPosition & SemanticMeaning.Value && hasValueSideModule(symbol))) {
return ClassificationType.moduleName;
}
}
return undefined;
/**
* Returns true if there exists a module that introduces entities on the value side.
*/
function hasValueSideModule(symbol: Symbol): boolean {
return forEach(symbol.declarations, declaration => {
return declaration.kind === SyntaxKind.ModuleDeclaration &&
getModuleInstanceState(declaration) === ModuleInstanceState.Instantiated;
});
}
}
function processNode(node: Node) {
// Only walk into nodes that intersect the requested span.
if (node && textSpanIntersectsWith(span, node.getFullStart(), node.getFullWidth())) {
const kind = node.kind;
checkForClassificationCancellation(cancellationToken, kind);
if (kind === SyntaxKind.Identifier && !nodeIsMissing(node)) {
const identifier = <Identifier>node;
// Only bother calling into the typechecker if this is an identifier that
// could possibly resolve to a type name. This makes classification run
// in a third of the time it would normally take.
if (classifiableNames[identifier.text]) {
const symbol = typeChecker.getSymbolAtLocation(node);
if (symbol) {
const type = classifySymbol(symbol, getMeaningFromLocation(node));
if (type) {
pushClassification(node.getStart(), node.getWidth(), type);
}
}
}
}
forEachChild(node, processNode);
}
}
}
function getClassificationTypeName(type: ClassificationType) {
switch (type) {
case ClassificationType.comment: return ClassificationTypeNames.comment;
case ClassificationType.identifier: return ClassificationTypeNames.identifier;
case ClassificationType.keyword: return ClassificationTypeNames.keyword;
case ClassificationType.numericLiteral: return ClassificationTypeNames.numericLiteral;
case ClassificationType.operator: return ClassificationTypeNames.operator;
case ClassificationType.stringLiteral: return ClassificationTypeNames.stringLiteral;
case ClassificationType.whiteSpace: return ClassificationTypeNames.whiteSpace;
case ClassificationType.text: return ClassificationTypeNames.text;
case ClassificationType.punctuation: return ClassificationTypeNames.punctuation;
case ClassificationType.className: return ClassificationTypeNames.className;
case ClassificationType.enumName: return ClassificationTypeNames.enumName;
case ClassificationType.interfaceName: return ClassificationTypeNames.interfaceName;
case ClassificationType.moduleName: return ClassificationTypeNames.moduleName;
case ClassificationType.typeParameterName: return ClassificationTypeNames.typeParameterName;
case ClassificationType.typeAliasName: return ClassificationTypeNames.typeAliasName;
case ClassificationType.parameterName: return ClassificationTypeNames.parameterName;
case ClassificationType.docCommentTagName: return ClassificationTypeNames.docCommentTagName;
case ClassificationType.jsxOpenTagName: return ClassificationTypeNames.jsxOpenTagName;
case ClassificationType.jsxCloseTagName: return ClassificationTypeNames.jsxCloseTagName;
case ClassificationType.jsxSelfClosingTagName: return ClassificationTypeNames.jsxSelfClosingTagName;
case ClassificationType.jsxAttribute: return ClassificationTypeNames.jsxAttribute;
case ClassificationType.jsxText: return ClassificationTypeNames.jsxText;
case ClassificationType.jsxAttributeStringLiteralValue: return ClassificationTypeNames.jsxAttributeStringLiteralValue;
}
}
function convertClassifications(classifications: Classifications): ClassifiedSpan[] {
Debug.assert(classifications.spans.length % 3 === 0);
const dense = classifications.spans;
const result: ClassifiedSpan[] = [];
for (let i = 0, n = dense.length; i < n; i += 3) {
result.push({
textSpan: createTextSpan(dense[i], dense[i + 1]),
classificationType: getClassificationTypeName(dense[i + 2])
});
}
return result;
}
/* @internal */
export function getSyntacticClassifications(cancellationToken: CancellationToken, sourceFile: SourceFile, span: TextSpan): ClassifiedSpan[] {
return convertClassifications(getEncodedSyntacticClassifications(cancellationToken, sourceFile, span));
}
/* @internal */
export function getEncodedSyntacticClassifications(cancellationToken: CancellationToken, sourceFile: SourceFile, span: TextSpan): Classifications {
const spanStart = span.start;
const spanLength = span.length;
// Make a scanner we can get trivia from.
const triviaScanner = createScanner(ScriptTarget.Latest, /*skipTrivia*/ false, sourceFile.languageVariant, sourceFile.text);
const mergeConflictScanner = createScanner(ScriptTarget.Latest, /*skipTrivia*/ false, sourceFile.languageVariant, sourceFile.text);
const result: number[] = [];
processElement(sourceFile);
return { spans: result, endOfLineState: EndOfLineState.None };
function pushClassification(start: number, length: number, type: ClassificationType) {
result.push(start);
result.push(length);
result.push(type);
}
function classifyLeadingTriviaAndGetTokenStart(token: Node): number {
triviaScanner.setTextPos(token.pos);
while (true) {
const start = triviaScanner.getTextPos();
// only bother scanning if we have something that could be trivia.
if (!couldStartTrivia(sourceFile.text, start)) {
return start;
}
const kind = triviaScanner.scan();
const end = triviaScanner.getTextPos();
const width = end - start;
// The moment we get something that isn't trivia, then stop processing.
if (!isTrivia(kind)) {
return start;
}
// Don't bother with newlines/whitespace.
if (kind === SyntaxKind.NewLineTrivia || kind === SyntaxKind.WhitespaceTrivia) {
continue;
}
// Only bother with the trivia if it at least intersects the span of interest.
if (isComment(kind)) {
classifyComment(token, kind, start, width);
// Classifying a comment might cause us to reuse the trivia scanner
// (because of jsdoc comments). So after we classify the comment make
// sure we set the scanner position back to where it needs to be.
triviaScanner.setTextPos(end);
continue;
}
if (kind === SyntaxKind.ConflictMarkerTrivia) {
const text = sourceFile.text;
const ch = text.charCodeAt(start);
// for the <<<<<<< and >>>>>>> markers, we just add them in as comments
// in the classification stream.
if (ch === CharacterCodes.lessThan || ch === CharacterCodes.greaterThan) {
pushClassification(start, width, ClassificationType.comment);
continue;
}
// for the ======== add a comment for the first line, and then lex all
// subsequent lines up until the end of the conflict marker.
Debug.assert(ch === CharacterCodes.equals);
classifyDisabledMergeCode(text, start, end);
}
}
}
function classifyComment(token: Node, kind: SyntaxKind, start: number, width: number) {
if (kind === SyntaxKind.MultiLineCommentTrivia) {
// See if this is a doc comment. If so, we'll classify certain portions of it
// specially.
const docCommentAndDiagnostics = parseIsolatedJSDocComment(sourceFile.text, start, width);
if (docCommentAndDiagnostics && docCommentAndDiagnostics.jsDoc) {
docCommentAndDiagnostics.jsDoc.parent = token;
classifyJSDocComment(docCommentAndDiagnostics.jsDoc);
return;
}
}
// Simple comment. Just add as is.
pushCommentRange(start, width);
}
function pushCommentRange(start: number, width: number) {
pushClassification(start, width, ClassificationType.comment);
}
function classifyJSDocComment(docComment: JSDoc) {
let pos = docComment.pos;
if (docComment.tags) {
for (const tag of docComment.tags) {
// As we walk through each tag, classify the portion of text from the end of
// the last tag (or the start of the entire doc comment) as 'comment'.
if (tag.pos !== pos) {
pushCommentRange(pos, tag.pos - pos);
}
pushClassification(tag.atToken.pos, tag.atToken.end - tag.atToken.pos, ClassificationType.punctuation);
pushClassification(tag.tagName.pos, tag.tagName.end - tag.tagName.pos, ClassificationType.docCommentTagName);
pos = tag.tagName.end;
switch (tag.kind) {
case SyntaxKind.JSDocParameterTag:
processJSDocParameterTag(<JSDocParameterTag>tag);
break;
case SyntaxKind.JSDocTemplateTag:
processJSDocTemplateTag(<JSDocTemplateTag>tag);
break;
case SyntaxKind.JSDocTypeTag:
processElement((<JSDocTypeTag>tag).typeExpression);
break;
case SyntaxKind.JSDocReturnTag:
processElement((<JSDocReturnTag>tag).typeExpression);
break;
}
pos = tag.end;
}
}
if (pos !== docComment.end) {
pushCommentRange(pos, docComment.end - pos);
}
return;
function processJSDocParameterTag(tag: JSDocParameterTag) {
if (tag.preParameterName) {
pushCommentRange(pos, tag.preParameterName.pos - pos);
pushClassification(tag.preParameterName.pos, tag.preParameterName.end - tag.preParameterName.pos, ClassificationType.parameterName);
pos = tag.preParameterName.end;
}
if (tag.typeExpression) {
pushCommentRange(pos, tag.typeExpression.pos - pos);
processElement(tag.typeExpression);
pos = tag.typeExpression.end;
}
if (tag.postParameterName) {
pushCommentRange(pos, tag.postParameterName.pos - pos);
pushClassification(tag.postParameterName.pos, tag.postParameterName.end - tag.postParameterName.pos, ClassificationType.parameterName);
pos = tag.postParameterName.end;
}
}
}
function processJSDocTemplateTag(tag: JSDocTemplateTag) {
for (const child of tag.getChildren()) {
processElement(child);
}
}
function classifyDisabledMergeCode(text: string, start: number, end: number) {
// Classify the line that the ======= marker is on as a comment. Then just lex
// all further tokens and add them to the result.
let i: number;
for (i = start; i < end; i++) {
if (isLineBreak(text.charCodeAt(i))) {
break;
}
}
pushClassification(start, i - start, ClassificationType.comment);
mergeConflictScanner.setTextPos(i);
while (mergeConflictScanner.getTextPos() < end) {
classifyDisabledCodeToken();
}
}
function classifyDisabledCodeToken() {
const start = mergeConflictScanner.getTextPos();
const tokenKind = mergeConflictScanner.scan();
const end = mergeConflictScanner.getTextPos();
const type = classifyTokenType(tokenKind);
if (type) {
pushClassification(start, end - start, type);
}
}
/**
* Returns true if node should be treated as classified and no further processing is required.
* False will mean that node is not classified and traverse routine should recurse into node contents.
*/
function tryClassifyNode(node: Node): boolean {
if (isJSDocTag(node)) {
return true;
}
if (nodeIsMissing(node)) {
return true;
}
const classifiedElementName = tryClassifyJsxElementName(node);
if (!isToken(node) && node.kind !== SyntaxKind.JsxText && classifiedElementName === undefined) {
return false;
}
const tokenStart = node.kind === SyntaxKind.JsxText ? node.pos : classifyLeadingTriviaAndGetTokenStart(node);
const tokenWidth = node.end - tokenStart;
Debug.assert(tokenWidth >= 0);
if (tokenWidth > 0) {
const type = classifiedElementName || classifyTokenType(node.kind, node);
if (type) {
pushClassification(tokenStart, tokenWidth, type);
}
}
return true;
}
function tryClassifyJsxElementName(token: Node): ClassificationType {
switch (token.parent && token.parent.kind) {
case SyntaxKind.JsxOpeningElement:
if ((<JsxOpeningElement>token.parent).tagName === token) {
return ClassificationType.jsxOpenTagName;
}
break;
case SyntaxKind.JsxClosingElement:
if ((<JsxClosingElement>token.parent).tagName === token) {
return ClassificationType.jsxCloseTagName;
}
break;
case SyntaxKind.JsxSelfClosingElement:
if ((<JsxSelfClosingElement>token.parent).tagName === token) {
return ClassificationType.jsxSelfClosingTagName;
}
break;
case SyntaxKind.JsxAttribute:
if ((<JsxAttribute>token.parent).name === token) {
return ClassificationType.jsxAttribute;
}
break;
}
return undefined;
}
// for accurate classification, the actual token should be passed in. however, for
// cases like 'disabled merge code' classification, we just get the token kind and
// classify based on that instead.
function classifyTokenType(tokenKind: SyntaxKind, token?: Node): ClassificationType {
if (isKeyword(tokenKind)) {
return ClassificationType.keyword;
}
// Special case < and > If they appear in a generic context they are punctuation,
// not operators.
if (tokenKind === SyntaxKind.LessThanToken || tokenKind === SyntaxKind.GreaterThanToken) {
// If the node owning the token has a type argument list or type parameter list, then
// we can effectively assume that a '<' and '>' belong to those lists.
if (token && getTypeArgumentOrTypeParameterList(token.parent)) {
return ClassificationType.punctuation;
}
}
if (isPunctuation(tokenKind)) {
if (token) {
if (tokenKind === SyntaxKind.EqualsToken) {
// the '=' in a variable declaration is special cased here.
if (token.parent.kind === SyntaxKind.VariableDeclaration ||
token.parent.kind === SyntaxKind.PropertyDeclaration ||
token.parent.kind === SyntaxKind.Parameter ||
token.parent.kind === SyntaxKind.JsxAttribute) {
return ClassificationType.operator;
}
}
if (token.parent.kind === SyntaxKind.BinaryExpression ||
token.parent.kind === SyntaxKind.PrefixUnaryExpression ||
token.parent.kind === SyntaxKind.PostfixUnaryExpression ||
token.parent.kind === SyntaxKind.ConditionalExpression) {
return ClassificationType.operator;
}
}
return ClassificationType.punctuation;
}
else if (tokenKind === SyntaxKind.NumericLiteral) {
return ClassificationType.numericLiteral;
}
else if (tokenKind === SyntaxKind.StringLiteral) {
return token.parent.kind === SyntaxKind.JsxAttribute ? ClassificationType.jsxAttributeStringLiteralValue : ClassificationType.stringLiteral;
}
else if (tokenKind === SyntaxKind.RegularExpressionLiteral) {
// TODO: we should get another classification type for these literals.
return ClassificationType.stringLiteral;
}
else if (isTemplateLiteralKind(tokenKind)) {
// TODO (drosen): we should *also* get another classification type for these literals.
return ClassificationType.stringLiteral;
}
else if (tokenKind === SyntaxKind.JsxText) {
return ClassificationType.jsxText;
}
else if (tokenKind === SyntaxKind.Identifier) {
if (token) {
switch (token.parent.kind) {
case SyntaxKind.ClassDeclaration:
if ((<ClassDeclaration>token.parent).name === token) {
return ClassificationType.className;
}
return;
case SyntaxKind.TypeParameter:
if ((<TypeParameterDeclaration>token.parent).name === token) {
return ClassificationType.typeParameterName;
}
return;
case SyntaxKind.InterfaceDeclaration:
if ((<InterfaceDeclaration>token.parent).name === token) {
return ClassificationType.interfaceName;
}
return;
case SyntaxKind.EnumDeclaration:
if ((<EnumDeclaration>token.parent).name === token) {
return ClassificationType.enumName;
}
return;
case SyntaxKind.ModuleDeclaration:
if ((<ModuleDeclaration>token.parent).name === token) {
return ClassificationType.moduleName;
}
return;
case SyntaxKind.Parameter:
if ((<ParameterDeclaration>token.parent).name === token) {
const isThis = token.kind === SyntaxKind.Identifier && (<Identifier>token).originalKeywordKind === SyntaxKind.ThisKeyword;
return isThis ? ClassificationType.keyword : ClassificationType.parameterName;
}
return;
}
}
return ClassificationType.identifier;
}
}
function processElement(element: Node) {
if (!element) {
return;
}
// Ignore nodes that don't intersect the original span to classify.
if (decodedTextSpanIntersectsWith(spanStart, spanLength, element.pos, element.getFullWidth())) {
checkForClassificationCancellation(cancellationToken, element.kind);
const children = element.getChildren(sourceFile);
for (let i = 0, n = children.length; i < n; i++) {
const child = children[i];
if (!tryClassifyNode(child)) {
// Recurse into our child nodes.
processElement(child);
}
}
}
}
}
}
File diff suppressed because it is too large Load Diff
+638
View File
@@ -0,0 +1,638 @@
/* @internal */
namespace ts.DocumentHighlights {
export function getDocumentHighlights(typeChecker: TypeChecker, cancellationToken: CancellationToken, sourceFile: SourceFile, position: number, sourceFilesToSearch: SourceFile[]): DocumentHighlights[] {
const node = getTouchingWord(sourceFile, position);
if (!node) {
return undefined;
}
return getSemanticDocumentHighlights(node) || getSyntacticDocumentHighlights(node);
function getHighlightSpanForNode(node: Node): HighlightSpan {
const start = node.getStart();
const end = node.getEnd();
return {
fileName: sourceFile.fileName,
textSpan: createTextSpanFromBounds(start, end),
kind: HighlightSpanKind.none
};
}
function getSemanticDocumentHighlights(node: Node): DocumentHighlights[] {
if (node.kind === SyntaxKind.Identifier ||
node.kind === SyntaxKind.ThisKeyword ||
node.kind === SyntaxKind.ThisType ||
node.kind === SyntaxKind.SuperKeyword ||
node.kind === SyntaxKind.StringLiteral ||
isLiteralNameOfPropertyDeclarationOrIndexAccess(node)) {
const referencedSymbols = FindAllReferences.getReferencedSymbolsForNode(typeChecker, cancellationToken, node, sourceFilesToSearch, /*findInStrings*/ false, /*findInComments*/ false, /*implementations*/false);
return convertReferencedSymbols(referencedSymbols);
}
return undefined;
function convertReferencedSymbols(referencedSymbols: ReferencedSymbol[]): DocumentHighlights[] {
if (!referencedSymbols) {
return undefined;
}
const fileNameToDocumentHighlights = createMap<DocumentHighlights>();
const result: DocumentHighlights[] = [];
for (const referencedSymbol of referencedSymbols) {
for (const referenceEntry of referencedSymbol.references) {
const fileName = referenceEntry.fileName;
let documentHighlights = fileNameToDocumentHighlights[fileName];
if (!documentHighlights) {
documentHighlights = { fileName, highlightSpans: [] };
fileNameToDocumentHighlights[fileName] = documentHighlights;
result.push(documentHighlights);
}
documentHighlights.highlightSpans.push({
textSpan: referenceEntry.textSpan,
kind: referenceEntry.isWriteAccess ? HighlightSpanKind.writtenReference : HighlightSpanKind.reference
});
}
}
return result;
}
}
function getSyntacticDocumentHighlights(node: Node): DocumentHighlights[] {
const fileName = sourceFile.fileName;
const highlightSpans = getHighlightSpans(node);
if (!highlightSpans || highlightSpans.length === 0) {
return undefined;
}
return [{ fileName, highlightSpans }];
// returns true if 'node' is defined and has a matching 'kind'.
function hasKind(node: Node, kind: SyntaxKind) {
return node !== undefined && node.kind === kind;
}
// Null-propagating 'parent' function.
function parent(node: Node): Node {
return node && node.parent;
}
function getHighlightSpans(node: Node): HighlightSpan[] {
if (node) {
switch (node.kind) {
case SyntaxKind.IfKeyword:
case SyntaxKind.ElseKeyword:
if (hasKind(node.parent, SyntaxKind.IfStatement)) {
return getIfElseOccurrences(<IfStatement>node.parent);
}
break;
case SyntaxKind.ReturnKeyword:
if (hasKind(node.parent, SyntaxKind.ReturnStatement)) {
return getReturnOccurrences(<ReturnStatement>node.parent);
}
break;
case SyntaxKind.ThrowKeyword:
if (hasKind(node.parent, SyntaxKind.ThrowStatement)) {
return getThrowOccurrences(<ThrowStatement>node.parent);
}
break;
case SyntaxKind.CatchKeyword:
if (hasKind(parent(parent(node)), SyntaxKind.TryStatement)) {
return getTryCatchFinallyOccurrences(<TryStatement>node.parent.parent);
}
break;
case SyntaxKind.TryKeyword:
case SyntaxKind.FinallyKeyword:
if (hasKind(parent(node), SyntaxKind.TryStatement)) {
return getTryCatchFinallyOccurrences(<TryStatement>node.parent);
}
break;
case SyntaxKind.SwitchKeyword:
if (hasKind(node.parent, SyntaxKind.SwitchStatement)) {
return getSwitchCaseDefaultOccurrences(<SwitchStatement>node.parent);
}
break;
case SyntaxKind.CaseKeyword:
case SyntaxKind.DefaultKeyword:
if (hasKind(parent(parent(parent(node))), SyntaxKind.SwitchStatement)) {
return getSwitchCaseDefaultOccurrences(<SwitchStatement>node.parent.parent.parent);
}
break;
case SyntaxKind.BreakKeyword:
case SyntaxKind.ContinueKeyword:
if (hasKind(node.parent, SyntaxKind.BreakStatement) || hasKind(node.parent, SyntaxKind.ContinueStatement)) {
return getBreakOrContinueStatementOccurrences(<BreakOrContinueStatement>node.parent);
}
break;
case SyntaxKind.ForKeyword:
if (hasKind(node.parent, SyntaxKind.ForStatement) ||
hasKind(node.parent, SyntaxKind.ForInStatement) ||
hasKind(node.parent, SyntaxKind.ForOfStatement)) {
return getLoopBreakContinueOccurrences(<IterationStatement>node.parent);
}
break;
case SyntaxKind.WhileKeyword:
case SyntaxKind.DoKeyword:
if (hasKind(node.parent, SyntaxKind.WhileStatement) || hasKind(node.parent, SyntaxKind.DoStatement)) {
return getLoopBreakContinueOccurrences(<IterationStatement>node.parent);
}
break;
case SyntaxKind.ConstructorKeyword:
if (hasKind(node.parent, SyntaxKind.Constructor)) {
return getConstructorOccurrences(<ConstructorDeclaration>node.parent);
}
break;
case SyntaxKind.GetKeyword:
case SyntaxKind.SetKeyword:
if (hasKind(node.parent, SyntaxKind.GetAccessor) || hasKind(node.parent, SyntaxKind.SetAccessor)) {
return getGetAndSetOccurrences(<AccessorDeclaration>node.parent);
}
break;
default:
if (isModifierKind(node.kind) && node.parent &&
(isDeclaration(node.parent) || node.parent.kind === SyntaxKind.VariableStatement)) {
return getModifierOccurrences(node.kind, node.parent);
}
}
}
return undefined;
}
/**
* Aggregates all throw-statements within this node *without* crossing
* into function boundaries and try-blocks with catch-clauses.
*/
function aggregateOwnedThrowStatements(node: Node): ThrowStatement[] {
const statementAccumulator: ThrowStatement[] = [];
aggregate(node);
return statementAccumulator;
function aggregate(node: Node): void {
if (node.kind === SyntaxKind.ThrowStatement) {
statementAccumulator.push(<ThrowStatement>node);
}
else if (node.kind === SyntaxKind.TryStatement) {
const tryStatement = <TryStatement>node;
if (tryStatement.catchClause) {
aggregate(tryStatement.catchClause);
}
else {
// Exceptions thrown within a try block lacking a catch clause
// are "owned" in the current context.
aggregate(tryStatement.tryBlock);
}
if (tryStatement.finallyBlock) {
aggregate(tryStatement.finallyBlock);
}
}
// Do not cross function boundaries.
else if (!isFunctionLike(node)) {
forEachChild(node, aggregate);
}
}
}
/**
* For lack of a better name, this function takes a throw statement and returns the
* nearest ancestor that is a try-block (whose try statement has a catch clause),
* function-block, or source file.
*/
function getThrowStatementOwner(throwStatement: ThrowStatement): Node {
let child: Node = throwStatement;
while (child.parent) {
const parent = child.parent;
if (isFunctionBlock(parent) || parent.kind === SyntaxKind.SourceFile) {
return parent;
}
// A throw-statement is only owned by a try-statement if the try-statement has
// a catch clause, and if the throw-statement occurs within the try block.
if (parent.kind === SyntaxKind.TryStatement) {
const tryStatement = <TryStatement>parent;
if (tryStatement.tryBlock === child && tryStatement.catchClause) {
return child;
}
}
child = parent;
}
return undefined;
}
function aggregateAllBreakAndContinueStatements(node: Node): BreakOrContinueStatement[] {
const statementAccumulator: BreakOrContinueStatement[] = [];
aggregate(node);
return statementAccumulator;
function aggregate(node: Node): void {
if (node.kind === SyntaxKind.BreakStatement || node.kind === SyntaxKind.ContinueStatement) {
statementAccumulator.push(<BreakOrContinueStatement>node);
}
// Do not cross function boundaries.
else if (!isFunctionLike(node)) {
forEachChild(node, aggregate);
}
}
}
function ownsBreakOrContinueStatement(owner: Node, statement: BreakOrContinueStatement): boolean {
const actualOwner = getBreakOrContinueOwner(statement);
return actualOwner && actualOwner === owner;
}
function getBreakOrContinueOwner(statement: BreakOrContinueStatement): Node {
for (let node = statement.parent; node; node = node.parent) {
switch (node.kind) {
case SyntaxKind.SwitchStatement:
if (statement.kind === SyntaxKind.ContinueStatement) {
continue;
}
// Fall through.
case SyntaxKind.ForStatement:
case SyntaxKind.ForInStatement:
case SyntaxKind.ForOfStatement:
case SyntaxKind.WhileStatement:
case SyntaxKind.DoStatement:
if (!statement.label || isLabeledBy(node, statement.label.text)) {
return node;
}
break;
default:
// Don't cross function boundaries.
if (isFunctionLike(node)) {
return undefined;
}
break;
}
}
return undefined;
}
function getModifierOccurrences(modifier: SyntaxKind, declaration: Node): HighlightSpan[] {
const container = declaration.parent;
// Make sure we only highlight the keyword when it makes sense to do so.
if (isAccessibilityModifier(modifier)) {
if (!(container.kind === SyntaxKind.ClassDeclaration ||
container.kind === SyntaxKind.ClassExpression ||
(declaration.kind === SyntaxKind.Parameter && hasKind(container, SyntaxKind.Constructor)))) {
return undefined;
}
}
else if (modifier === SyntaxKind.StaticKeyword) {
if (!(container.kind === SyntaxKind.ClassDeclaration || container.kind === SyntaxKind.ClassExpression)) {
return undefined;
}
}
else if (modifier === SyntaxKind.ExportKeyword || modifier === SyntaxKind.DeclareKeyword) {
if (!(container.kind === SyntaxKind.ModuleBlock || container.kind === SyntaxKind.SourceFile)) {
return undefined;
}
}
else if (modifier === SyntaxKind.AbstractKeyword) {
if (!(container.kind === SyntaxKind.ClassDeclaration || declaration.kind === SyntaxKind.ClassDeclaration)) {
return undefined;
}
}
else {
// unsupported modifier
return undefined;
}
const keywords: Node[] = [];
const modifierFlag: ModifierFlags = getFlagFromModifier(modifier);
let nodes: Node[];
switch (container.kind) {
case SyntaxKind.ModuleBlock:
case SyntaxKind.SourceFile:
// Container is either a class declaration or the declaration is a classDeclaration
if (modifierFlag & ModifierFlags.Abstract) {
nodes = (<Node[]>(<ClassDeclaration>declaration).members).concat(declaration);
}
else {
nodes = (<Block>container).statements;
}
break;
case SyntaxKind.Constructor:
nodes = (<Node[]>(<ConstructorDeclaration>container).parameters).concat(
(<ClassDeclaration>container.parent).members);
break;
case SyntaxKind.ClassDeclaration:
case SyntaxKind.ClassExpression:
nodes = (<ClassLikeDeclaration>container).members;
// If we're an accessibility modifier, we're in an instance member and should search
// the constructor's parameter list for instance members as well.
if (modifierFlag & ModifierFlags.AccessibilityModifier) {
const constructor = forEach((<ClassLikeDeclaration>container).members, member => {
return member.kind === SyntaxKind.Constructor && <ConstructorDeclaration>member;
});
if (constructor) {
nodes = nodes.concat(constructor.parameters);
}
}
else if (modifierFlag & ModifierFlags.Abstract) {
nodes = nodes.concat(container);
}
break;
default:
Debug.fail("Invalid container kind.");
}
forEach(nodes, node => {
if (getModifierFlags(node) & modifierFlag) {
forEach(node.modifiers, child => pushKeywordIf(keywords, child, modifier));
}
});
return map(keywords, getHighlightSpanForNode);
function getFlagFromModifier(modifier: SyntaxKind) {
switch (modifier) {
case SyntaxKind.PublicKeyword:
return ModifierFlags.Public;
case SyntaxKind.PrivateKeyword:
return ModifierFlags.Private;
case SyntaxKind.ProtectedKeyword:
return ModifierFlags.Protected;
case SyntaxKind.StaticKeyword:
return ModifierFlags.Static;
case SyntaxKind.ExportKeyword:
return ModifierFlags.Export;
case SyntaxKind.DeclareKeyword:
return ModifierFlags.Ambient;
case SyntaxKind.AbstractKeyword:
return ModifierFlags.Abstract;
default:
Debug.fail();
}
}
}
function pushKeywordIf(keywordList: Node[], token: Node, ...expected: SyntaxKind[]): boolean {
if (token && contains(expected, token.kind)) {
keywordList.push(token);
return true;
}
return false;
}
function getGetAndSetOccurrences(accessorDeclaration: AccessorDeclaration): HighlightSpan[] {
const keywords: Node[] = [];
tryPushAccessorKeyword(accessorDeclaration.symbol, SyntaxKind.GetAccessor);
tryPushAccessorKeyword(accessorDeclaration.symbol, SyntaxKind.SetAccessor);
return map(keywords, getHighlightSpanForNode);
function tryPushAccessorKeyword(accessorSymbol: Symbol, accessorKind: SyntaxKind): void {
const accessor = getDeclarationOfKind(accessorSymbol, accessorKind);
if (accessor) {
forEach(accessor.getChildren(), child => pushKeywordIf(keywords, child, SyntaxKind.GetKeyword, SyntaxKind.SetKeyword));
}
}
}
function getConstructorOccurrences(constructorDeclaration: ConstructorDeclaration): HighlightSpan[] {
const declarations = constructorDeclaration.symbol.getDeclarations();
const keywords: Node[] = [];
forEach(declarations, declaration => {
forEach(declaration.getChildren(), token => {
return pushKeywordIf(keywords, token, SyntaxKind.ConstructorKeyword);
});
});
return map(keywords, getHighlightSpanForNode);
}
function getLoopBreakContinueOccurrences(loopNode: IterationStatement): HighlightSpan[] {
const keywords: Node[] = [];
if (pushKeywordIf(keywords, loopNode.getFirstToken(), SyntaxKind.ForKeyword, SyntaxKind.WhileKeyword, SyntaxKind.DoKeyword)) {
// If we succeeded and got a do-while loop, then start looking for a 'while' keyword.
if (loopNode.kind === SyntaxKind.DoStatement) {
const loopTokens = loopNode.getChildren();
for (let i = loopTokens.length - 1; i >= 0; i--) {
if (pushKeywordIf(keywords, loopTokens[i], SyntaxKind.WhileKeyword)) {
break;
}
}
}
}
const breaksAndContinues = aggregateAllBreakAndContinueStatements(loopNode.statement);
forEach(breaksAndContinues, statement => {
if (ownsBreakOrContinueStatement(loopNode, statement)) {
pushKeywordIf(keywords, statement.getFirstToken(), SyntaxKind.BreakKeyword, SyntaxKind.ContinueKeyword);
}
});
return map(keywords, getHighlightSpanForNode);
}
function getBreakOrContinueStatementOccurrences(breakOrContinueStatement: BreakOrContinueStatement): HighlightSpan[] {
const owner = getBreakOrContinueOwner(breakOrContinueStatement);
if (owner) {
switch (owner.kind) {
case SyntaxKind.ForStatement:
case SyntaxKind.ForInStatement:
case SyntaxKind.ForOfStatement:
case SyntaxKind.DoStatement:
case SyntaxKind.WhileStatement:
return getLoopBreakContinueOccurrences(<IterationStatement>owner);
case SyntaxKind.SwitchStatement:
return getSwitchCaseDefaultOccurrences(<SwitchStatement>owner);
}
}
return undefined;
}
function getSwitchCaseDefaultOccurrences(switchStatement: SwitchStatement): HighlightSpan[] {
const keywords: Node[] = [];
pushKeywordIf(keywords, switchStatement.getFirstToken(), SyntaxKind.SwitchKeyword);
// Go through each clause in the switch statement, collecting the 'case'/'default' keywords.
forEach(switchStatement.caseBlock.clauses, clause => {
pushKeywordIf(keywords, clause.getFirstToken(), SyntaxKind.CaseKeyword, SyntaxKind.DefaultKeyword);
const breaksAndContinues = aggregateAllBreakAndContinueStatements(clause);
forEach(breaksAndContinues, statement => {
if (ownsBreakOrContinueStatement(switchStatement, statement)) {
pushKeywordIf(keywords, statement.getFirstToken(), SyntaxKind.BreakKeyword);
}
});
});
return map(keywords, getHighlightSpanForNode);
}
function getTryCatchFinallyOccurrences(tryStatement: TryStatement): HighlightSpan[] {
const keywords: Node[] = [];
pushKeywordIf(keywords, tryStatement.getFirstToken(), SyntaxKind.TryKeyword);
if (tryStatement.catchClause) {
pushKeywordIf(keywords, tryStatement.catchClause.getFirstToken(), SyntaxKind.CatchKeyword);
}
if (tryStatement.finallyBlock) {
const finallyKeyword = findChildOfKind(tryStatement, SyntaxKind.FinallyKeyword, sourceFile);
pushKeywordIf(keywords, finallyKeyword, SyntaxKind.FinallyKeyword);
}
return map(keywords, getHighlightSpanForNode);
}
function getThrowOccurrences(throwStatement: ThrowStatement): HighlightSpan[] {
const owner = getThrowStatementOwner(throwStatement);
if (!owner) {
return undefined;
}
const keywords: Node[] = [];
forEach(aggregateOwnedThrowStatements(owner), throwStatement => {
pushKeywordIf(keywords, throwStatement.getFirstToken(), SyntaxKind.ThrowKeyword);
});
// If the "owner" is a function, then we equate 'return' and 'throw' statements in their
// ability to "jump out" of the function, and include occurrences for both.
if (isFunctionBlock(owner)) {
forEachReturnStatement(<Block>owner, returnStatement => {
pushKeywordIf(keywords, returnStatement.getFirstToken(), SyntaxKind.ReturnKeyword);
});
}
return map(keywords, getHighlightSpanForNode);
}
function getReturnOccurrences(returnStatement: ReturnStatement): HighlightSpan[] {
const func = <FunctionLikeDeclaration>getContainingFunction(returnStatement);
// If we didn't find a containing function with a block body, bail out.
if (!(func && hasKind(func.body, SyntaxKind.Block))) {
return undefined;
}
const keywords: Node[] = [];
forEachReturnStatement(<Block>func.body, returnStatement => {
pushKeywordIf(keywords, returnStatement.getFirstToken(), SyntaxKind.ReturnKeyword);
});
// Include 'throw' statements that do not occur within a try block.
forEach(aggregateOwnedThrowStatements(func.body), throwStatement => {
pushKeywordIf(keywords, throwStatement.getFirstToken(), SyntaxKind.ThrowKeyword);
});
return map(keywords, getHighlightSpanForNode);
}
function getIfElseOccurrences(ifStatement: IfStatement): HighlightSpan[] {
const keywords: Node[] = [];
// Traverse upwards through all parent if-statements linked by their else-branches.
while (hasKind(ifStatement.parent, SyntaxKind.IfStatement) && (<IfStatement>ifStatement.parent).elseStatement === ifStatement) {
ifStatement = <IfStatement>ifStatement.parent;
}
// Now traverse back down through the else branches, aggregating if/else keywords of if-statements.
while (ifStatement) {
const children = ifStatement.getChildren();
pushKeywordIf(keywords, children[0], SyntaxKind.IfKeyword);
// Generally the 'else' keyword is second-to-last, so we traverse backwards.
for (let i = children.length - 1; i >= 0; i--) {
if (pushKeywordIf(keywords, children[i], SyntaxKind.ElseKeyword)) {
break;
}
}
if (!hasKind(ifStatement.elseStatement, SyntaxKind.IfStatement)) {
break;
}
ifStatement = <IfStatement>ifStatement.elseStatement;
}
const result: HighlightSpan[] = [];
// We'd like to highlight else/ifs together if they are only separated by whitespace
// (i.e. the keywords are separated by no comments, no newlines).
for (let i = 0; i < keywords.length; i++) {
if (keywords[i].kind === SyntaxKind.ElseKeyword && i < keywords.length - 1) {
const elseKeyword = keywords[i];
const ifKeyword = keywords[i + 1]; // this *should* always be an 'if' keyword.
let shouldCombindElseAndIf = true;
// Avoid recalculating getStart() by iterating backwards.
for (let j = ifKeyword.getStart() - 1; j >= elseKeyword.end; j--) {
if (!isWhiteSpaceSingleLine(sourceFile.text.charCodeAt(j))) {
shouldCombindElseAndIf = false;
break;
}
}
if (shouldCombindElseAndIf) {
result.push({
fileName: fileName,
textSpan: createTextSpanFromBounds(elseKeyword.getStart(), ifKeyword.end),
kind: HighlightSpanKind.reference
});
i++; // skip the next keyword
continue;
}
}
// Ordinary case: just highlight the keyword.
result.push(getHighlightSpanForNode(keywords[i]));
}
return result;
}
}
}
/**
* Whether or not a 'node' is preceded by a label of the given string.
* Note: 'node' cannot be a SourceFile.
*/
function isLabeledBy(node: Node, labelName: string) {
for (let owner = node.parent; owner.kind === SyntaxKind.LabeledStatement; owner = owner.parent) {
if ((<LabeledStatement>owner).label.text === labelName) {
return true;
}
}
return false;
}
}
+240
View File
@@ -0,0 +1,240 @@
namespace ts {
/**
* The document registry represents a store of SourceFile objects that can be shared between
* multiple LanguageService instances. A LanguageService instance holds on the SourceFile (AST)
* of files in the context.
* SourceFile objects account for most of the memory usage by the language service. Sharing
* the same DocumentRegistry instance between different instances of LanguageService allow
* for more efficient memory utilization since all projects will share at least the library
* file (lib.d.ts).
*
* A more advanced use of the document registry is to serialize sourceFile objects to disk
* and re-hydrate them when needed.
*
* To create a default DocumentRegistry, use createDocumentRegistry to create one, and pass it
* to all subsequent createLanguageService calls.
*/
export interface DocumentRegistry {
/**
* Request a stored SourceFile with a given fileName and compilationSettings.
* The first call to acquire will call createLanguageServiceSourceFile to generate
* the SourceFile if was not found in the registry.
*
* @param fileName The name of the file requested
* @param compilationSettings Some compilation settings like target affects the
* shape of a the resulting SourceFile. This allows the DocumentRegistry to store
* multiple copies of the same file for different compilation settings.
* @parm scriptSnapshot Text of the file. Only used if the file was not found
* in the registry and a new one was created.
* @parm version Current version of the file. Only used if the file was not found
* in the registry and a new one was created.
*/
acquireDocument(
fileName: string,
compilationSettings: CompilerOptions,
scriptSnapshot: IScriptSnapshot,
version: string,
scriptKind?: ScriptKind): SourceFile;
acquireDocumentWithKey(
fileName: string,
path: Path,
compilationSettings: CompilerOptions,
key: DocumentRegistryBucketKey,
scriptSnapshot: IScriptSnapshot,
version: string,
scriptKind?: ScriptKind): SourceFile;
/**
* Request an updated version of an already existing SourceFile with a given fileName
* and compilationSettings. The update will in-turn call updateLanguageServiceSourceFile
* to get an updated SourceFile.
*
* @param fileName The name of the file requested
* @param compilationSettings Some compilation settings like target affects the
* shape of a the resulting SourceFile. This allows the DocumentRegistry to store
* multiple copies of the same file for different compilation settings.
* @param scriptSnapshot Text of the file.
* @param version Current version of the file.
*/
updateDocument(
fileName: string,
compilationSettings: CompilerOptions,
scriptSnapshot: IScriptSnapshot,
version: string,
scriptKind?: ScriptKind): SourceFile;
updateDocumentWithKey(
fileName: string,
path: Path,
compilationSettings: CompilerOptions,
key: DocumentRegistryBucketKey,
scriptSnapshot: IScriptSnapshot,
version: string,
scriptKind?: ScriptKind): SourceFile;
getKeyForCompilationSettings(settings: CompilerOptions): DocumentRegistryBucketKey;
/**
* Informs the DocumentRegistry that a file is not needed any longer.
*
* Note: It is not allowed to call release on a SourceFile that was not acquired from
* this registry originally.
*
* @param fileName The name of the file to be released
* @param compilationSettings The compilation settings used to acquire the file
*/
releaseDocument(fileName: string, compilationSettings: CompilerOptions): void;
releaseDocumentWithKey(path: Path, key: DocumentRegistryBucketKey): void;
reportStats(): string;
}
export type DocumentRegistryBucketKey = string & { __bucketKey: any };
interface DocumentRegistryEntry {
sourceFile: SourceFile;
// The number of language services that this source file is referenced in. When no more
// language services are referencing the file, then the file can be removed from the
// registry.
languageServiceRefCount: number;
owners: string[];
}
export function createDocumentRegistry(useCaseSensitiveFileNames?: boolean, currentDirectory = ""): DocumentRegistry {
// Maps from compiler setting target (ES3, ES5, etc.) to all the cached documents we have
// for those settings.
const buckets = createMap<FileMap<DocumentRegistryEntry>>();
const getCanonicalFileName = createGetCanonicalFileName(!!useCaseSensitiveFileNames);
function getKeyForCompilationSettings(settings: CompilerOptions): DocumentRegistryBucketKey {
return <DocumentRegistryBucketKey>`_${settings.target}|${settings.module}|${settings.noResolve}|${settings.jsx}|${settings.allowJs}|${settings.baseUrl}|${JSON.stringify(settings.typeRoots)}|${JSON.stringify(settings.rootDirs)}|${JSON.stringify(settings.paths)}`;
}
function getBucketForCompilationSettings(key: DocumentRegistryBucketKey, createIfMissing: boolean): FileMap<DocumentRegistryEntry> {
let bucket = buckets[key];
if (!bucket && createIfMissing) {
buckets[key] = bucket = createFileMap<DocumentRegistryEntry>();
}
return bucket;
}
function reportStats() {
const bucketInfoArray = Object.keys(buckets).filter(name => name && name.charAt(0) === "_").map(name => {
const entries = buckets[name];
const sourceFiles: { name: string; refCount: number; references: string[]; }[] = [];
entries.forEachValue((key, entry) => {
sourceFiles.push({
name: key,
refCount: entry.languageServiceRefCount,
references: entry.owners.slice(0)
});
});
sourceFiles.sort((x, y) => y.refCount - x.refCount);
return {
bucket: name,
sourceFiles
};
});
return JSON.stringify(bucketInfoArray, undefined, 2);
}
function acquireDocument(fileName: string, compilationSettings: CompilerOptions, scriptSnapshot: IScriptSnapshot, version: string, scriptKind?: ScriptKind): SourceFile {
const path = toPath(fileName, currentDirectory, getCanonicalFileName);
const key = getKeyForCompilationSettings(compilationSettings);
return acquireDocumentWithKey(fileName, path, compilationSettings, key, scriptSnapshot, version, scriptKind);
}
function acquireDocumentWithKey(fileName: string, path: Path, compilationSettings: CompilerOptions, key: DocumentRegistryBucketKey, scriptSnapshot: IScriptSnapshot, version: string, scriptKind?: ScriptKind): SourceFile {
return acquireOrUpdateDocument(fileName, path, compilationSettings, key, scriptSnapshot, version, /*acquiring*/ true, scriptKind);
}
function updateDocument(fileName: string, compilationSettings: CompilerOptions, scriptSnapshot: IScriptSnapshot, version: string, scriptKind?: ScriptKind): SourceFile {
const path = toPath(fileName, currentDirectory, getCanonicalFileName);
const key = getKeyForCompilationSettings(compilationSettings);
return updateDocumentWithKey(fileName, path, compilationSettings, key, scriptSnapshot, version, scriptKind);
}
function updateDocumentWithKey(fileName: string, path: Path, compilationSettings: CompilerOptions, key: DocumentRegistryBucketKey, scriptSnapshot: IScriptSnapshot, version: string, scriptKind?: ScriptKind): SourceFile {
return acquireOrUpdateDocument(fileName, path, compilationSettings, key, scriptSnapshot, version, /*acquiring*/ false, scriptKind);
}
function acquireOrUpdateDocument(
fileName: string,
path: Path,
compilationSettings: CompilerOptions,
key: DocumentRegistryBucketKey,
scriptSnapshot: IScriptSnapshot,
version: string,
acquiring: boolean,
scriptKind?: ScriptKind): SourceFile {
const bucket = getBucketForCompilationSettings(key, /*createIfMissing*/ true);
let entry = bucket.get(path);
if (!entry) {
Debug.assert(acquiring, "How could we be trying to update a document that the registry doesn't have?");
// Have never seen this file with these settings. Create a new source file for it.
const sourceFile = createLanguageServiceSourceFile(fileName, scriptSnapshot, compilationSettings.target, version, /*setNodeParents*/ false, scriptKind);
entry = {
sourceFile: sourceFile,
languageServiceRefCount: 0,
owners: []
};
bucket.set(path, entry);
}
else {
// We have an entry for this file. However, it may be for a different version of
// the script snapshot. If so, update it appropriately. Otherwise, we can just
// return it as is.
if (entry.sourceFile.version !== version) {
entry.sourceFile = updateLanguageServiceSourceFile(entry.sourceFile, scriptSnapshot, version,
scriptSnapshot.getChangeRange(entry.sourceFile.scriptSnapshot));
}
}
// If we're acquiring, then this is the first time this LS is asking for this document.
// Increase our ref count so we know there's another LS using the document. If we're
// not acquiring, then that means the LS is 'updating' the file instead, and that means
// it has already acquired the document previously. As such, we do not need to increase
// the ref count.
if (acquiring) {
entry.languageServiceRefCount++;
}
return entry.sourceFile;
}
function releaseDocument(fileName: string, compilationSettings: CompilerOptions): void {
const path = toPath(fileName, currentDirectory, getCanonicalFileName);
const key = getKeyForCompilationSettings(compilationSettings);
return releaseDocumentWithKey(path, key);
}
function releaseDocumentWithKey(path: Path, key: DocumentRegistryBucketKey): void {
const bucket = getBucketForCompilationSettings(key, /*createIfMissing*/false);
Debug.assert(bucket !== undefined);
const entry = bucket.get(path);
entry.languageServiceRefCount--;
Debug.assert(entry.languageServiceRefCount >= 0);
if (entry.languageServiceRefCount === 0) {
bucket.remove(path);
}
}
return {
acquireDocument,
acquireDocumentWithKey,
updateDocument,
updateDocumentWithKey,
releaseDocument,
releaseDocumentWithKey,
reportStats,
getKeyForCompilationSettings
};
}
}
File diff suppressed because it is too large Load Diff
+15 -5
View File
@@ -50,6 +50,8 @@ namespace ts.formatting {
// Insert a space after { and before } in single-line contexts, but remove space from empty object literals {}.
public SpaceAfterOpenBrace: Rule;
public SpaceBeforeCloseBrace: Rule;
public NoSpaceAfterOpenBrace: Rule;
public NoSpaceBeforeCloseBrace: Rule;
public NoSpaceBetweenEmptyBraceBrackets: Rule;
// Insert new line after { and before } in multi-line contexts.
@@ -136,7 +138,6 @@ namespace ts.formatting {
public NoSpaceAfterOpenAngularBracket: Rule;
public NoSpaceBeforeCloseAngularBracket: Rule;
public NoSpaceAfterCloseAngularBracket: Rule;
public NoSpaceAfterTypeAssertion: Rule;
// Remove spaces in empty interface literals. e.g.: x: {}
public NoSpaceBetweenEmptyInterfaceBraceBrackets: Rule;
@@ -238,6 +239,10 @@ namespace ts.formatting {
public NoSpaceBeforeEqualInJsxAttribute: Rule;
public NoSpaceAfterEqualInJsxAttribute: Rule;
// No space after type assertions
public NoSpaceAfterTypeAssertion: Rule;
public SpaceAfterTypeAssertion: Rule;
constructor() {
///
/// Common Rules
@@ -287,6 +292,8 @@ namespace ts.formatting {
// Insert a space after { and before } in single-line contexts, but remove space from empty object literals {}.
this.SpaceAfterOpenBrace = new Rule(RuleDescriptor.create3(SyntaxKind.OpenBraceToken, Shared.TokenRange.Any), RuleOperation.create2(new RuleOperationContext(Rules.IsSingleLineBlockContext), RuleAction.Space));
this.SpaceBeforeCloseBrace = new Rule(RuleDescriptor.create2(Shared.TokenRange.Any, SyntaxKind.CloseBraceToken), RuleOperation.create2(new RuleOperationContext(Rules.IsSingleLineBlockContext), RuleAction.Space));
this.NoSpaceAfterOpenBrace = new Rule(RuleDescriptor.create3(SyntaxKind.OpenBraceToken, Shared.TokenRange.Any), RuleOperation.create2(new RuleOperationContext(Rules.IsSingleLineBlockContext), RuleAction.Delete));
this.NoSpaceBeforeCloseBrace = new Rule(RuleDescriptor.create2(Shared.TokenRange.Any, SyntaxKind.CloseBraceToken), RuleOperation.create2(new RuleOperationContext(Rules.IsSingleLineBlockContext), RuleAction.Delete));
this.NoSpaceBetweenEmptyBraceBrackets = new Rule(RuleDescriptor.create1(SyntaxKind.OpenBraceToken, SyntaxKind.CloseBraceToken), RuleOperation.create2(new RuleOperationContext(Rules.IsNonJsxSameLineTokenContext, Rules.IsObjectContext), RuleAction.Delete));
// Insert new line after { and before } in multi-line contexts.
@@ -371,7 +378,6 @@ namespace ts.formatting {
this.NoSpaceAfterOpenAngularBracket = new Rule(RuleDescriptor.create3(SyntaxKind.LessThanToken, Shared.TokenRange.Any), RuleOperation.create2(new RuleOperationContext(Rules.IsNonJsxSameLineTokenContext, Rules.IsTypeArgumentOrParameterOrAssertionContext), RuleAction.Delete));
this.NoSpaceBeforeCloseAngularBracket = new Rule(RuleDescriptor.create2(Shared.TokenRange.Any, SyntaxKind.GreaterThanToken), RuleOperation.create2(new RuleOperationContext(Rules.IsNonJsxSameLineTokenContext, Rules.IsTypeArgumentOrParameterOrAssertionContext), RuleAction.Delete));
this.NoSpaceAfterCloseAngularBracket = new Rule(RuleDescriptor.create3(SyntaxKind.GreaterThanToken, Shared.TokenRange.FromTokens([SyntaxKind.OpenParenToken, SyntaxKind.OpenBracketToken, SyntaxKind.GreaterThanToken, SyntaxKind.CommaToken])), RuleOperation.create2(new RuleOperationContext(Rules.IsNonJsxSameLineTokenContext, Rules.IsTypeArgumentOrParameterOrAssertionContext), RuleAction.Delete));
this.NoSpaceAfterTypeAssertion = new Rule(RuleDescriptor.create3(SyntaxKind.GreaterThanToken, Shared.TokenRange.Any), RuleOperation.create2(new RuleOperationContext(Rules.IsNonJsxSameLineTokenContext, Rules.IsTypeAssertionContext), RuleAction.Delete));
// Remove spaces in empty interface literals. e.g.: x: {}
this.NoSpaceBetweenEmptyInterfaceBraceBrackets = new Rule(RuleDescriptor.create1(SyntaxKind.OpenBraceToken, SyntaxKind.CloseBraceToken), RuleOperation.create2(new RuleOperationContext(Rules.IsNonJsxSameLineTokenContext, Rules.IsObjectTypeContext), RuleAction.Delete));
@@ -414,7 +420,7 @@ namespace ts.formatting {
this.SpaceAfterPostdecrementWhenFollowedBySubtract,
this.SpaceAfterSubtractWhenFollowedByUnaryMinus, this.SpaceAfterSubtractWhenFollowedByPredecrement,
this.NoSpaceAfterCloseBrace,
this.SpaceAfterOpenBrace, this.SpaceBeforeCloseBrace, this.NewLineBeforeCloseBraceInBlockContext,
this.NewLineBeforeCloseBraceInBlockContext,
this.SpaceAfterCloseBrace, this.SpaceBetweenCloseBraceAndElse, this.SpaceBetweenCloseBraceAndWhile, this.NoSpaceBetweenEmptyBraceBrackets,
this.NoSpaceBetweenFunctionKeywordAndStar, this.SpaceAfterStarInGeneratorDeclaration,
this.SpaceAfterFunctionInFuncDecl, this.NewLineAfterOpenBraceInBlockContext, this.SpaceAfterGetSetInMember,
@@ -443,7 +449,6 @@ namespace ts.formatting {
this.NoSpaceAfterOpenAngularBracket,
this.NoSpaceBeforeCloseAngularBracket,
this.NoSpaceAfterCloseAngularBracket,
this.NoSpaceAfterTypeAssertion,
this.SpaceBeforeAt,
this.NoSpaceAfterAt,
this.SpaceAfterDecorator,
@@ -522,6 +527,11 @@ namespace ts.formatting {
// Insert space after function keyword for anonymous functions
this.SpaceAfterAnonymousFunctionKeyword = new Rule(RuleDescriptor.create1(SyntaxKind.FunctionKeyword, SyntaxKind.OpenParenToken), RuleOperation.create2(new RuleOperationContext(Rules.IsFunctionDeclContext), RuleAction.Space));
this.NoSpaceAfterAnonymousFunctionKeyword = new Rule(RuleDescriptor.create1(SyntaxKind.FunctionKeyword, SyntaxKind.OpenParenToken), RuleOperation.create2(new RuleOperationContext(Rules.IsFunctionDeclContext), RuleAction.Delete));
// No space after type assertion
this.NoSpaceAfterTypeAssertion = new Rule(RuleDescriptor.create3(SyntaxKind.GreaterThanToken, Shared.TokenRange.Any), RuleOperation.create2(new RuleOperationContext(Rules.IsNonJsxSameLineTokenContext, Rules.IsTypeAssertionContext), RuleAction.Delete));
this.SpaceAfterTypeAssertion = new Rule(RuleDescriptor.create3(SyntaxKind.GreaterThanToken, Shared.TokenRange.Any), RuleOperation.create2(new RuleOperationContext(Rules.IsNonJsxSameLineTokenContext, Rules.IsTypeAssertionContext), RuleAction.Space));
}
///
@@ -789,7 +799,7 @@ namespace ts.formatting {
}
static NodeIsInDecoratorContext(node: Node): boolean {
while (isExpression(node)) {
while (isPartOfExpression(node)) {
node = node.parent;
}
return node.kind === SyntaxKind.Decorator;
+20
View File
@@ -81,6 +81,19 @@ namespace ts.formatting {
rules.push(this.globalRules.NoSpaceBetweenBrackets);
}
// The default value of InsertSpaceAfterOpeningAndBeforeClosingNonemptyBraces is true
// so if the option is undefined, we should treat it as true as well
if (options.insertSpaceAfterOpeningAndBeforeClosingNonemptyBraces !== false) {
rules.push(this.globalRules.SpaceAfterOpenBrace);
rules.push(this.globalRules.SpaceBeforeCloseBrace);
rules.push(this.globalRules.NoSpaceBetweenEmptyBraceBrackets);
}
else {
rules.push(this.globalRules.NoSpaceAfterOpenBrace);
rules.push(this.globalRules.NoSpaceBeforeCloseBrace);
rules.push(this.globalRules.NoSpaceBetweenEmptyBraceBrackets);
}
if (options.insertSpaceAfterOpeningAndBeforeClosingTemplateStringBraces) {
rules.push(this.globalRules.SpaceAfterTemplateHeadAndMiddle);
rules.push(this.globalRules.SpaceBeforeTemplateMiddleAndTail);
@@ -124,6 +137,13 @@ namespace ts.formatting {
rules.push(this.globalRules.NewLineBeforeOpenBraceInTypeScriptDeclWithBlock);
}
if (options.insertSpaceAfterTypeAssertion) {
rules.push(this.globalRules.SpaceAfterTypeAssertion);
}
else {
rules.push(this.globalRules.NoSpaceAfterTypeAssertion);
}
rules = rules.concat(this.globalRules.LowPriorityCommonRules);
return rules;
+1 -1
View File
@@ -208,7 +208,7 @@ namespace ts.formatting {
// - parent is SourceFile - by default immediate children of SourceFile are not indented except when user indents them manually
// - parent and child are not on the same line
const useActualIndentation =
(isDeclaration(current) || isStatement(current)) &&
(isDeclaration(current) || isStatementButNotDeclaration(current)) &&
(parent.kind === SyntaxKind.SourceFile || !parentAndChildShareLine);
if (!useActualIndentation) {
+256
View File
@@ -0,0 +1,256 @@
/* @internal */
namespace ts.GoToDefinition {
export function getDefinitionAtPosition(program: Program, sourceFile: SourceFile, position: number): DefinitionInfo[] {
/// Triple slash reference comments
const comment = findReferenceInPosition(sourceFile.referencedFiles, position);
if (comment) {
const referenceFile = tryResolveScriptReference(program, sourceFile, comment);
if (referenceFile) {
return [getDefinitionInfoForFileReference(comment.fileName, referenceFile.fileName)];
}
return undefined;
}
// Type reference directives
const typeReferenceDirective = findReferenceInPosition(sourceFile.typeReferenceDirectives, position);
if (typeReferenceDirective) {
const referenceFile = program.getResolvedTypeReferenceDirectives()[typeReferenceDirective.fileName];
if (referenceFile && referenceFile.resolvedFileName) {
return [getDefinitionInfoForFileReference(typeReferenceDirective.fileName, referenceFile.resolvedFileName)];
}
return undefined;
}
const node = getTouchingPropertyName(sourceFile, position);
if (node === sourceFile) {
return undefined;
}
// Labels
if (isJumpStatementTarget(node)) {
const labelName = (<Identifier>node).text;
const label = getTargetLabel((<BreakOrContinueStatement>node.parent), (<Identifier>node).text);
return label ? [createDefinitionInfo(label, ScriptElementKind.label, labelName, /*containerName*/ undefined)] : undefined;
}
const typeChecker = program.getTypeChecker();
const calledDeclaration = tryGetSignatureDeclaration(typeChecker, node);
if (calledDeclaration) {
return [createDefinitionFromSignatureDeclaration(typeChecker, calledDeclaration)];
}
let symbol = typeChecker.getSymbolAtLocation(node);
// Could not find a symbol e.g. node is string or number keyword,
// or the symbol was an internal symbol and does not have a declaration e.g. undefined symbol
if (!symbol) {
return undefined;
}
// If this is an alias, and the request came at the declaration location
// get the aliased symbol instead. This allows for goto def on an import e.g.
// import {A, B} from "mod";
// to jump to the implementation directly.
if (symbol.flags & SymbolFlags.Alias) {
const declaration = symbol.declarations[0];
// Go to the original declaration for cases:
//
// (1) when the aliased symbol was declared in the location(parent).
// (2) when the aliased symbol is originating from a named import.
//
if (node.kind === SyntaxKind.Identifier &&
(node.parent === declaration ||
(declaration.kind === SyntaxKind.ImportSpecifier && declaration.parent && declaration.parent.kind === SyntaxKind.NamedImports))) {
symbol = typeChecker.getAliasedSymbol(symbol);
}
}
// Because name in short-hand property assignment has two different meanings: property name and property value,
// using go-to-definition at such position should go to the variable declaration of the property value rather than
// go to the declaration of the property name (in this case stay at the same position). However, if go-to-definition
// is performed at the location of property access, we would like to go to definition of the property in the short-hand
// assignment. This case and others are handled by the following code.
if (node.parent.kind === SyntaxKind.ShorthandPropertyAssignment) {
const shorthandSymbol = typeChecker.getShorthandAssignmentValueSymbol(symbol.valueDeclaration);
if (!shorthandSymbol) {
return [];
}
const shorthandDeclarations = shorthandSymbol.getDeclarations();
const shorthandSymbolKind = SymbolDisplay.getSymbolKind(typeChecker, shorthandSymbol, node);
const shorthandSymbolName = typeChecker.symbolToString(shorthandSymbol);
const shorthandContainerName = typeChecker.symbolToString(symbol.parent, node);
return map(shorthandDeclarations,
declaration => createDefinitionInfo(declaration, shorthandSymbolKind, shorthandSymbolName, shorthandContainerName));
}
return getDefinitionFromSymbol(typeChecker, symbol, node);
}
/// Goto type
export function getTypeDefinitionAtPosition(typeChecker: TypeChecker, sourceFile: SourceFile, position: number): DefinitionInfo[] {
const node = getTouchingPropertyName(sourceFile, position);
if (node === sourceFile) {
return undefined;
}
const symbol = typeChecker.getSymbolAtLocation(node);
if (!symbol) {
return undefined;
}
const type = typeChecker.getTypeOfSymbolAtLocation(symbol, node);
if (!type) {
return undefined;
}
if (type.flags & TypeFlags.Union && !(type.flags & TypeFlags.Enum)) {
const result: DefinitionInfo[] = [];
forEach((<UnionType>type).types, t => {
if (t.symbol) {
addRange(/*to*/ result, /*from*/ getDefinitionFromSymbol(typeChecker, t.symbol, node));
}
});
return result;
}
if (!type.symbol) {
return undefined;
}
return getDefinitionFromSymbol(typeChecker, type.symbol, node);
}
function getDefinitionFromSymbol(typeChecker: TypeChecker, symbol: Symbol, node: Node): DefinitionInfo[] {
const result: DefinitionInfo[] = [];
const declarations = symbol.getDeclarations();
const { symbolName, symbolKind, containerName } = getSymbolInfo(typeChecker, symbol, node);
if (!tryAddConstructSignature(symbol, node, symbolKind, symbolName, containerName, result) &&
!tryAddCallSignature(symbol, node, symbolKind, symbolName, containerName, result)) {
// Just add all the declarations.
forEach(declarations, declaration => {
result.push(createDefinitionInfo(declaration, symbolKind, symbolName, containerName));
});
}
return result;
function tryAddConstructSignature(symbol: Symbol, location: Node, symbolKind: string, symbolName: string, containerName: string, result: DefinitionInfo[]) {
// Applicable only if we are in a new expression, or we are on a constructor declaration
// and in either case the symbol has a construct signature definition, i.e. class
if (isNewExpressionTarget(location) || location.kind === SyntaxKind.ConstructorKeyword) {
if (symbol.flags & SymbolFlags.Class) {
// Find the first class-like declaration and try to get the construct signature.
for (const declaration of symbol.getDeclarations()) {
if (isClassLike(declaration)) {
return tryAddSignature(declaration.members,
/*selectConstructors*/ true,
symbolKind,
symbolName,
containerName,
result);
}
}
Debug.fail("Expected declaration to have at least one class-like declaration");
}
}
return false;
}
function tryAddCallSignature(symbol: Symbol, location: Node, symbolKind: string, symbolName: string, containerName: string, result: DefinitionInfo[]) {
if (isCallExpressionTarget(location) || isNewExpressionTarget(location) || isNameOfFunctionDeclaration(location)) {
return tryAddSignature(symbol.declarations, /*selectConstructors*/ false, symbolKind, symbolName, containerName, result);
}
return false;
}
function tryAddSignature(signatureDeclarations: Declaration[], selectConstructors: boolean, symbolKind: string, symbolName: string, containerName: string, result: DefinitionInfo[]) {
const declarations: Declaration[] = [];
let definition: Declaration;
forEach(signatureDeclarations, d => {
if ((selectConstructors && d.kind === SyntaxKind.Constructor) ||
(!selectConstructors && (d.kind === SyntaxKind.FunctionDeclaration || d.kind === SyntaxKind.MethodDeclaration || d.kind === SyntaxKind.MethodSignature))) {
declarations.push(d);
if ((<FunctionLikeDeclaration>d).body) definition = d;
}
});
if (definition) {
result.push(createDefinitionInfo(definition, symbolKind, symbolName, containerName));
return true;
}
else if (declarations.length) {
result.push(createDefinitionInfo(lastOrUndefined(declarations), symbolKind, symbolName, containerName));
return true;
}
return false;
}
}
function createDefinitionInfo(node: Node, symbolKind: string, symbolName: string, containerName: string): DefinitionInfo {
return {
fileName: node.getSourceFile().fileName,
textSpan: createTextSpanFromBounds(node.getStart(), node.getEnd()),
kind: symbolKind,
name: symbolName,
containerKind: undefined,
containerName
};
}
function getSymbolInfo(typeChecker: TypeChecker, symbol: Symbol, node: Node) {
return {
symbolName: typeChecker.symbolToString(symbol), // Do not get scoped name, just the name of the symbol
symbolKind: SymbolDisplay.getSymbolKind(typeChecker, symbol, node),
containerName: symbol.parent ? typeChecker.symbolToString(symbol.parent, node) : ""
};
}
function createDefinitionFromSignatureDeclaration(typeChecker: TypeChecker, decl: SignatureDeclaration): DefinitionInfo {
const { symbolName, symbolKind, containerName } = getSymbolInfo(typeChecker, decl.symbol, decl);
return createDefinitionInfo(decl, symbolKind, symbolName, containerName);
}
function findReferenceInPosition(refs: FileReference[], pos: number): FileReference {
for (const ref of refs) {
if (ref.pos <= pos && pos < ref.end) {
return ref;
}
}
return undefined;
}
function getDefinitionInfoForFileReference(name: string, targetFileName: string): DefinitionInfo {
return {
fileName: targetFileName,
textSpan: createTextSpanFromBounds(0, 0),
kind: ScriptElementKind.scriptElement,
name: name,
containerName: undefined,
containerKind: undefined
};
}
/** Returns a CallLikeExpression where `node` is the target being invoked. */
function getAncestorCallLikeExpression(node: Node): CallLikeExpression | undefined {
const target = climbPastManyPropertyAccesses(node);
const callLike = target.parent;
return callLike && isCallLikeExpression(callLike) && getInvokedExpression(callLike) === target && callLike;
}
function climbPastManyPropertyAccesses(node: Node): Node {
return isRightSideOfPropertyAccess(node) ? climbPastManyPropertyAccesses(node.parent) : node;
}
function tryGetSignatureDeclaration(typeChecker: TypeChecker, node: Node): SignatureDeclaration | undefined {
const callLike = getAncestorCallLikeExpression(node);
return callLike && typeChecker.getResolvedSignature(callLike).declaration;
}
}
+27
View File
@@ -0,0 +1,27 @@
/* @internal */
namespace ts.GoToImplementation {
export function getImplementationAtPosition(typeChecker: TypeChecker, cancellationToken: CancellationToken, sourceFiles: SourceFile[], node: Node): ImplementationLocation[] {
// If invoked directly on a shorthand property assignment, then return
// the declaration of the symbol being assigned (not the symbol being assigned to).
if (node.parent.kind === SyntaxKind.ShorthandPropertyAssignment) {
const result: ReferenceEntry[] = [];
FindAllReferences.getReferenceEntriesForShorthandPropertyAssignment(node, typeChecker, result);
return result.length > 0 ? result : undefined;
}
else if (node.kind === SyntaxKind.SuperKeyword || isSuperProperty(node.parent)) {
// References to and accesses on the super keyword only have one possible implementation, so no
// need to "Find all References"
const symbol = typeChecker.getSymbolAtLocation(node);
return symbol.valueDeclaration && [FindAllReferences.getReferenceEntryFromNode(symbol.valueDeclaration)];
}
else {
// Perform "Find all References" and retrieve only those that are implementations
const referencedSymbols = FindAllReferences.getReferencedSymbolsForNode(typeChecker, cancellationToken,
node, sourceFiles, /*findInStrings*/false, /*findInComments*/false, /*implementations*/true);
const result = flatMap(referencedSymbols, symbol =>
map(symbol.references, ({ textSpan, fileName }) => ({ textSpan, fileName })));
return result && result.length > 0 ? result : undefined;
}
}
}
+243
View File
@@ -0,0 +1,243 @@
/* @internal */
namespace ts.JsDoc {
const jsDocTagNames = [
"augments",
"author",
"argument",
"borrows",
"class",
"constant",
"constructor",
"constructs",
"default",
"deprecated",
"description",
"event",
"example",
"extends",
"field",
"fileOverview",
"function",
"ignore",
"inner",
"lends",
"link",
"memberOf",
"name",
"namespace",
"param",
"private",
"property",
"public",
"requires",
"returns",
"see",
"since",
"static",
"throws",
"type",
"typedef",
"property",
"prop",
"version"
];
let jsDocCompletionEntries: CompletionEntry[];
export function getJsDocCommentsFromDeclarations(declarations: Declaration[], name: string, canUseParsedParamTagComments: boolean) {
// Only collect doc comments from duplicate declarations once:
// In case of a union property there might be same declaration multiple times
// which only varies in type parameter
// Eg. const a: Array<string> | Array<number>; a.length
// The property length will have two declarations of property length coming
// from Array<T> - Array<string> and Array<number>
const documentationComment = <SymbolDisplayPart[]>[];
forEachUnique(declarations, declaration => {
const comments = getJSDocComments(declaration, /*checkParentVariableStatement*/ true);
if (!comments) {
return;
}
for (const comment of comments) {
if (comment) {
if (documentationComment.length) {
documentationComment.push(lineBreakPart());
}
documentationComment.push(textPart(comment));
}
}
});
return documentationComment;
}
/**
* Iterates through 'array' by index and performs the callback on each element of array until the callback
* returns a truthy value, then returns that value.
* If no such value is found, the callback is applied to each element of array and undefined is returned.
*/
function forEachUnique<T, U>(array: T[], callback: (element: T, index: number) => U): U {
if (array) {
for (let i = 0, len = array.length; i < len; i++) {
if (indexOf(array, array[i]) === i) {
const result = callback(array[i], i);
if (result) {
return result;
}
}
}
}
return undefined;
}
export function getAllJsDocCompletionEntries(): CompletionEntry[] {
return jsDocCompletionEntries || (jsDocCompletionEntries = ts.map(jsDocTagNames, tagName => {
return {
name: tagName,
kind: ScriptElementKind.keyword,
kindModifiers: "",
sortText: "0",
};
}));
}
/**
* Checks if position points to a valid position to add JSDoc comments, and if so,
* returns the appropriate template. Otherwise returns an empty string.
* Valid positions are
* - outside of comments, statements, and expressions, and
* - preceding a:
* - function/constructor/method declaration
* - class declarations
* - variable statements
* - namespace declarations
*
* Hosts should ideally check that:
* - The line is all whitespace up to 'position' before performing the insertion.
* - If the keystroke sequence "/\*\*" induced the call, we also check that the next
* non-whitespace character is '*', which (approximately) indicates whether we added
* the second '*' to complete an existing (JSDoc) comment.
* @param fileName The file in which to perform the check.
* @param position The (character-indexed) position in the file where the check should
* be performed.
*/
export function getDocCommentTemplateAtPosition(newLine: string, sourceFile: SourceFile, position: number): TextInsertion {
// Check if in a context where we don't want to perform any insertion
if (isInString(sourceFile, position) || isInComment(sourceFile, position) || hasDocComment(sourceFile, position)) {
return undefined;
}
const tokenAtPos = getTokenAtPosition(sourceFile, position);
const tokenStart = tokenAtPos.getStart();
if (!tokenAtPos || tokenStart < position) {
return undefined;
}
// TODO: add support for:
// - enums/enum members
// - interfaces
// - property declarations
// - potentially property assignments
let commentOwner: Node;
findOwner: for (commentOwner = tokenAtPos; commentOwner; commentOwner = commentOwner.parent) {
switch (commentOwner.kind) {
case SyntaxKind.FunctionDeclaration:
case SyntaxKind.MethodDeclaration:
case SyntaxKind.Constructor:
case SyntaxKind.ClassDeclaration:
case SyntaxKind.VariableStatement:
break findOwner;
case SyntaxKind.SourceFile:
return undefined;
case SyntaxKind.ModuleDeclaration:
// If in walking up the tree, we hit a a nested namespace declaration,
// then we must be somewhere within a dotted namespace name; however we don't
// want to give back a JSDoc template for the 'b' or 'c' in 'namespace a.b.c { }'.
if (commentOwner.parent.kind === SyntaxKind.ModuleDeclaration) {
return undefined;
}
break findOwner;
}
}
if (!commentOwner || commentOwner.getStart() < position) {
return undefined;
}
const parameters = getParametersForJsDocOwningNode(commentOwner);
const posLineAndChar = sourceFile.getLineAndCharacterOfPosition(position);
const lineStart = sourceFile.getLineStarts()[posLineAndChar.line];
const indentationStr = sourceFile.text.substr(lineStart, posLineAndChar.character);
let docParams = "";
for (let i = 0, numParams = parameters.length; i < numParams; i++) {
const currentName = parameters[i].name;
const paramName = currentName.kind === SyntaxKind.Identifier ?
(<Identifier>currentName).text :
"param" + i;
docParams += `${indentationStr} * @param ${paramName}${newLine}`;
}
// A doc comment consists of the following
// * The opening comment line
// * the first line (without a param) for the object's untagged info (this is also where the caret ends up)
// * the '@param'-tagged lines
// * TODO: other tags.
// * the closing comment line
// * if the caret was directly in front of the object, then we add an extra line and indentation.
const preamble = "/**" + newLine +
indentationStr + " * ";
const result =
preamble + newLine +
docParams +
indentationStr + " */" +
(tokenStart === position ? newLine + indentationStr : "");
return { newText: result, caretOffset: preamble.length };
}
function getParametersForJsDocOwningNode(commentOwner: Node): ParameterDeclaration[] {
if (isFunctionLike(commentOwner)) {
return commentOwner.parameters;
}
if (commentOwner.kind === SyntaxKind.VariableStatement) {
const varStatement = <VariableStatement>commentOwner;
const varDeclarations = varStatement.declarationList.declarations;
if (varDeclarations.length === 1 && varDeclarations[0].initializer) {
return getParametersFromRightHandSideOfAssignment(varDeclarations[0].initializer);
}
}
return emptyArray;
}
/**
* Digs into an an initializer or RHS operand of an assignment operation
* to get the parameters of an apt signature corresponding to a
* function expression or a class expression.
*
* @param rightHandSide the expression which may contain an appropriate set of parameters
* @returns the parameters of a signature found on the RHS if one exists; otherwise 'emptyArray'.
*/
function getParametersFromRightHandSideOfAssignment(rightHandSide: Expression): ParameterDeclaration[] {
while (rightHandSide.kind === SyntaxKind.ParenthesizedExpression) {
rightHandSide = (<ParenthesizedExpression>rightHandSide).expression;
}
switch (rightHandSide.kind) {
case SyntaxKind.FunctionExpression:
case SyntaxKind.ArrowFunction:
return (<FunctionExpression>rightHandSide).parameters;
case SyntaxKind.ClassExpression:
for (const member of (<ClassExpression>rightHandSide).members) {
if (member.kind === SyntaxKind.Constructor) {
return (<ConstructorDeclaration>member).parameters;
}
}
break;
}
return emptyArray;
}
}
+6 -5
View File
@@ -2,7 +2,7 @@
namespace ts.NavigateTo {
type RawNavigateToItem = { name: string; fileName: string; matchKind: PatternMatchKind; isCaseSensitive: boolean; declaration: Declaration };
export function getNavigateToItems(program: Program, checker: TypeChecker, cancellationToken: CancellationToken, searchValue: string, maxResultCount: number, excludeDts: boolean): NavigateToItem[] {
export function getNavigateToItems(sourceFiles: SourceFile[], checker: TypeChecker, cancellationToken: CancellationToken, searchValue: string, maxResultCount: number, excludeDtsFiles: boolean): NavigateToItem[] {
const patternMatcher = createPatternMatcher(searchValue);
let rawItems: RawNavigateToItem[] = [];
@@ -10,9 +10,13 @@ namespace ts.NavigateTo {
const baseSensitivity: Intl.CollatorOptions = { sensitivity: "base" };
// Search the declarations in all files and output matched NavigateToItem into array of NavigateToItem[]
forEach(program.getSourceFiles(), sourceFile => {
forEach(sourceFiles, sourceFile => {
cancellationToken.throwIfCancellationRequested();
if (excludeDtsFiles && fileExtensionIs(sourceFile.fileName, ".d.ts")) {
return;
}
const nameToDeclarations = sourceFile.getNamedDeclarations();
for (const name in nameToDeclarations) {
const declarations = nameToDeclarations[name];
@@ -43,9 +47,6 @@ namespace ts.NavigateTo {
const fileName = sourceFile.fileName;
const matchKind = bestMatchKind(matches);
if (excludeDts && fileExtensionIs(declaration.getSourceFile().fileName, ".d.ts")) {
continue;
}
rawItems.push({ name, fileName, matchKind, isCaseSensitive: allMatchesAreCaseSensitive(matches), declaration });
}
}
+9 -10
View File
@@ -218,15 +218,13 @@ namespace ts.NavigationBar {
break;
default:
if (node.jsDocComments) {
for (const jsDocComment of node.jsDocComments) {
for (const tag of jsDocComment.tags) {
if (tag.kind === SyntaxKind.JSDocTypedefTag) {
addLeafNode(tag);
}
forEach(node.jsDocComments, jsDocComment => {
forEach(jsDocComment.tags, tag => {
if (tag.kind === SyntaxKind.JSDocTypedefTag) {
addLeafNode(tag);
}
}
}
});
});
forEachChild(node, addChildrenRecursively);
}
@@ -397,7 +395,7 @@ namespace ts.NavigationBar {
case SyntaxKind.FunctionExpression:
case SyntaxKind.ClassDeclaration:
case SyntaxKind.ClassExpression:
if (node.flags & NodeFlags.Default) {
if (getModifierFlags(node) & ModifierFlags.Default) {
return "default";
}
return getFunctionOrClassName(<ArrowFunction | FunctionExpression | ClassExpression>node);
@@ -466,6 +464,7 @@ namespace ts.NavigationBar {
case SyntaxKind.MethodDeclaration:
case SyntaxKind.GetAccessor:
case SyntaxKind.SetAccessor:
case SyntaxKind.VariableDeclaration:
return hasSomeImportantChild(item);
case SyntaxKind.ArrowFunction:
@@ -595,7 +594,7 @@ namespace ts.NavigationBar {
return nodeText((node.parent as PropertyAssignment).name);
}
// Default exports are named "default"
else if (node.flags & NodeFlags.Default) {
else if (getModifierFlags(node) & ModifierFlags.Default) {
return "default";
}
else {
+359
View File
@@ -0,0 +1,359 @@
namespace ts {
export function preProcessFile(sourceText: string, readImportFiles = true, detectJavaScriptImports = false): PreProcessedFileInfo {
const referencedFiles: FileReference[] = [];
const typeReferenceDirectives: FileReference[] = [];
const importedFiles: FileReference[] = [];
let ambientExternalModules: { ref: FileReference, depth: number }[];
let isNoDefaultLib = false;
let braceNesting = 0;
// assume that text represent an external module if it contains at least one top level import/export
// ambient modules that are found inside external modules are interpreted as module augmentations
let externalModule = false;
function nextToken() {
const token = scanner.scan();
if (token === SyntaxKind.OpenBraceToken) {
braceNesting++;
}
else if (token === SyntaxKind.CloseBraceToken) {
braceNesting--;
}
return token;
}
function processTripleSlashDirectives(): void {
const commentRanges = getLeadingCommentRanges(sourceText, 0);
forEach(commentRanges, commentRange => {
const comment = sourceText.substring(commentRange.pos, commentRange.end);
const referencePathMatchResult = getFileReferenceFromReferencePath(comment, commentRange);
if (referencePathMatchResult) {
isNoDefaultLib = referencePathMatchResult.isNoDefaultLib;
const fileReference = referencePathMatchResult.fileReference;
if (fileReference) {
const collection = referencePathMatchResult.isTypeReferenceDirective
? typeReferenceDirectives
: referencedFiles;
collection.push(fileReference);
}
}
});
}
function getFileReference() {
const file = scanner.getTokenValue();
const pos = scanner.getTokenPos();
return {
fileName: file,
pos: pos,
end: pos + file.length
};
}
function recordAmbientExternalModule(): void {
if (!ambientExternalModules) {
ambientExternalModules = [];
}
ambientExternalModules.push({ ref: getFileReference(), depth: braceNesting });
}
function recordModuleName() {
importedFiles.push(getFileReference());
markAsExternalModuleIfTopLevel();
}
function markAsExternalModuleIfTopLevel() {
if (braceNesting === 0) {
externalModule = true;
}
}
/**
* Returns true if at least one token was consumed from the stream
*/
function tryConsumeDeclare(): boolean {
let token = scanner.getToken();
if (token === SyntaxKind.DeclareKeyword) {
// declare module "mod"
token = nextToken();
if (token === SyntaxKind.ModuleKeyword) {
token = nextToken();
if (token === SyntaxKind.StringLiteral) {
recordAmbientExternalModule();
}
}
return true;
}
return false;
}
/**
* Returns true if at least one token was consumed from the stream
*/
function tryConsumeImport(): boolean {
let token = scanner.getToken();
if (token === SyntaxKind.ImportKeyword) {
token = nextToken();
if (token === SyntaxKind.StringLiteral) {
// import "mod";
recordModuleName();
return true;
}
else {
if (token === SyntaxKind.Identifier || isKeyword(token)) {
token = nextToken();
if (token === SyntaxKind.FromKeyword) {
token = nextToken();
if (token === SyntaxKind.StringLiteral) {
// import d from "mod";
recordModuleName();
return true;
}
}
else if (token === SyntaxKind.EqualsToken) {
if (tryConsumeRequireCall(/*skipCurrentToken*/ true)) {
return true;
}
}
else if (token === SyntaxKind.CommaToken) {
// consume comma and keep going
token = nextToken();
}
else {
// unknown syntax
return true;
}
}
if (token === SyntaxKind.OpenBraceToken) {
token = nextToken();
// consume "{ a as B, c, d as D}" clauses
// make sure that it stops on EOF
while (token !== SyntaxKind.CloseBraceToken && token !== SyntaxKind.EndOfFileToken) {
token = nextToken();
}
if (token === SyntaxKind.CloseBraceToken) {
token = nextToken();
if (token === SyntaxKind.FromKeyword) {
token = nextToken();
if (token === SyntaxKind.StringLiteral) {
// import {a as A} from "mod";
// import d, {a, b as B} from "mod"
recordModuleName();
}
}
}
}
else if (token === SyntaxKind.AsteriskToken) {
token = nextToken();
if (token === SyntaxKind.AsKeyword) {
token = nextToken();
if (token === SyntaxKind.Identifier || isKeyword(token)) {
token = nextToken();
if (token === SyntaxKind.FromKeyword) {
token = nextToken();
if (token === SyntaxKind.StringLiteral) {
// import * as NS from "mod"
// import d, * as NS from "mod"
recordModuleName();
}
}
}
}
}
}
return true;
}
return false;
}
function tryConsumeExport(): boolean {
let token = scanner.getToken();
if (token === SyntaxKind.ExportKeyword) {
markAsExternalModuleIfTopLevel();
token = nextToken();
if (token === SyntaxKind.OpenBraceToken) {
token = nextToken();
// consume "{ a as B, c, d as D}" clauses
// make sure it stops on EOF
while (token !== SyntaxKind.CloseBraceToken && token !== SyntaxKind.EndOfFileToken) {
token = nextToken();
}
if (token === SyntaxKind.CloseBraceToken) {
token = nextToken();
if (token === SyntaxKind.FromKeyword) {
token = nextToken();
if (token === SyntaxKind.StringLiteral) {
// export {a as A} from "mod";
// export {a, b as B} from "mod"
recordModuleName();
}
}
}
}
else if (token === SyntaxKind.AsteriskToken) {
token = nextToken();
if (token === SyntaxKind.FromKeyword) {
token = nextToken();
if (token === SyntaxKind.StringLiteral) {
// export * from "mod"
recordModuleName();
}
}
}
else if (token === SyntaxKind.ImportKeyword) {
token = nextToken();
if (token === SyntaxKind.Identifier || isKeyword(token)) {
token = nextToken();
if (token === SyntaxKind.EqualsToken) {
if (tryConsumeRequireCall(/*skipCurrentToken*/ true)) {
return true;
}
}
}
}
return true;
}
return false;
}
function tryConsumeRequireCall(skipCurrentToken: boolean): boolean {
let token = skipCurrentToken ? nextToken() : scanner.getToken();
if (token === SyntaxKind.RequireKeyword) {
token = nextToken();
if (token === SyntaxKind.OpenParenToken) {
token = nextToken();
if (token === SyntaxKind.StringLiteral) {
// require("mod");
recordModuleName();
}
}
return true;
}
return false;
}
function tryConsumeDefine(): boolean {
let token = scanner.getToken();
if (token === SyntaxKind.Identifier && scanner.getTokenValue() === "define") {
token = nextToken();
if (token !== SyntaxKind.OpenParenToken) {
return true;
}
token = nextToken();
if (token === SyntaxKind.StringLiteral) {
// looks like define ("modname", ... - skip string literal and comma
token = nextToken();
if (token === SyntaxKind.CommaToken) {
token = nextToken();
}
else {
// unexpected token
return true;
}
}
// should be start of dependency list
if (token !== SyntaxKind.OpenBracketToken) {
return true;
}
// skip open bracket
token = nextToken();
let i = 0;
// scan until ']' or EOF
while (token !== SyntaxKind.CloseBracketToken && token !== SyntaxKind.EndOfFileToken) {
// record string literals as module names
if (token === SyntaxKind.StringLiteral) {
recordModuleName();
i++;
}
token = nextToken();
}
return true;
}
return false;
}
function processImports(): void {
scanner.setText(sourceText);
nextToken();
// Look for:
// import "mod";
// import d from "mod"
// import {a as A } from "mod";
// import * as NS from "mod"
// import d, {a, b as B} from "mod"
// import i = require("mod");
//
// export * from "mod"
// export {a as b} from "mod"
// export import i = require("mod")
// (for JavaScript files) require("mod")
while (true) {
if (scanner.getToken() === SyntaxKind.EndOfFileToken) {
break;
}
// check if at least one of alternative have moved scanner forward
if (tryConsumeDeclare() ||
tryConsumeImport() ||
tryConsumeExport() ||
(detectJavaScriptImports && (tryConsumeRequireCall(/*skipCurrentToken*/ false) || tryConsumeDefine()))) {
continue;
}
else {
nextToken();
}
}
scanner.setText(undefined);
}
if (readImportFiles) {
processImports();
}
processTripleSlashDirectives();
if (externalModule) {
// for external modules module all nested ambient modules are augmentations
if (ambientExternalModules) {
// move all detected ambient modules to imported files since they need to be resolved
for (const decl of ambientExternalModules) {
importedFiles.push(decl.ref);
}
}
return { referencedFiles, typeReferenceDirectives, importedFiles, isLibFile: isNoDefaultLib, ambientExternalModules: undefined };
}
else {
// for global scripts ambient modules still can have augmentations - look for ambient modules with depth > 0
let ambientModuleNames: string[];
if (ambientExternalModules) {
for (const decl of ambientExternalModules) {
if (decl.depth === 0) {
if (!ambientModuleNames) {
ambientModuleNames = [];
}
ambientModuleNames.push(decl.ref.fileName);
}
else {
importedFiles.push(decl.ref);
}
}
}
return { referencedFiles, typeReferenceDirectives, importedFiles, isLibFile: isNoDefaultLib, ambientExternalModules: ambientModuleNames };
}
}
}
+98
View File
@@ -0,0 +1,98 @@
/* @internal */
namespace ts.Rename {
export function getRenameInfo(typeChecker: TypeChecker, defaultLibFileName: string, getCanonicalFileName: (fileName: string) => string, sourceFile: SourceFile, position: number): RenameInfo {
const canonicalDefaultLibName = getCanonicalFileName(ts.normalizePath(defaultLibFileName));
const node = getTouchingWord(sourceFile, position, /*includeJsDocComment*/ true);
if (node) {
if (node.kind === SyntaxKind.Identifier ||
node.kind === SyntaxKind.StringLiteral ||
isLiteralNameOfPropertyDeclarationOrIndexAccess(node) ||
isThis(node)) {
const symbol = typeChecker.getSymbolAtLocation(node);
// Only allow a symbol to be renamed if it actually has at least one declaration.
if (symbol) {
const declarations = symbol.getDeclarations();
if (declarations && declarations.length > 0) {
// Disallow rename for elements that are defined in the standard TypeScript library.
if (forEach(declarations, isDefinedInLibraryFile)) {
return getRenameInfoError(getLocaleSpecificMessage(Diagnostics.You_cannot_rename_elements_that_are_defined_in_the_standard_TypeScript_library));
}
const displayName = stripQuotes(getDeclaredName(typeChecker, symbol, node));
const kind = SymbolDisplay.getSymbolKind(typeChecker, symbol, node);
if (kind) {
return {
canRename: true,
kind,
displayName,
localizedErrorMessage: undefined,
fullDisplayName: typeChecker.getFullyQualifiedName(symbol),
kindModifiers: SymbolDisplay.getSymbolModifiers(symbol),
triggerSpan: createTriggerSpanForNode(node, sourceFile)
};
}
}
}
else if (node.kind === SyntaxKind.StringLiteral) {
const type = getStringLiteralTypeForNode(<StringLiteral>node, typeChecker);
if (type) {
if (isDefinedInLibraryFile(node)) {
return getRenameInfoError(getLocaleSpecificMessage(Diagnostics.You_cannot_rename_elements_that_are_defined_in_the_standard_TypeScript_library));
}
else {
const displayName = stripQuotes(type.text);
return {
canRename: true,
kind: ScriptElementKind.variableElement,
displayName,
localizedErrorMessage: undefined,
fullDisplayName: displayName,
kindModifiers: ScriptElementKindModifier.none,
triggerSpan: createTriggerSpanForNode(node, sourceFile)
};
}
}
}
}
}
return getRenameInfoError(getLocaleSpecificMessage(Diagnostics.You_cannot_rename_this_element));
function getRenameInfoError(localizedErrorMessage: string): RenameInfo {
return {
canRename: false,
localizedErrorMessage: localizedErrorMessage,
displayName: undefined,
fullDisplayName: undefined,
kind: undefined,
kindModifiers: undefined,
triggerSpan: undefined
};
}
function isDefinedInLibraryFile(declaration: Node) {
if (defaultLibFileName) {
const sourceFile = declaration.getSourceFile();
const canonicalName = getCanonicalFileName(ts.normalizePath(sourceFile.fileName));
if (canonicalName === canonicalDefaultLibName) {
return true;
}
}
return false;
}
function createTriggerSpanForNode(node: Node, sourceFile: SourceFile) {
let start = node.getStart(sourceFile);
let width = node.getWidth(sourceFile);
if (node.kind === SyntaxKind.StringLiteral) {
// Exclude the quotes
start += 1;
width -= 2;
}
return createTextSpan(start, width);
}
}
}
+164 -7761
View File
File diff suppressed because it is too large Load Diff
+38 -4
View File
@@ -178,6 +178,12 @@ namespace ts {
*/
getTypeDefinitionAtPosition(fileName: string, position: number): string;
/**
* Returns a JSON-encoded value of the type:
* { fileName: string; textSpan: { start: number; length: number}; }[]
*/
getImplementationAtPosition(fileName: string, position: number): string;
/**
* Returns a JSON-encoded value of the type:
* { fileName: string; textSpan: { start: number; length: number}; isWriteAccess: boolean, isDefinition?: boolean }[]
@@ -210,7 +216,7 @@ namespace ts {
* Returns a JSON-encoded value of the type:
* { name: string; kind: string; kindModifiers: string; containerName: string; containerKind: string; matchKind: string; fileName: string; textSpan: { start: number; length: number}; } [] = [];
*/
getNavigateToItems(searchValue: string, maxResultCount?: number): string;
getNavigateToItems(searchValue: string, maxResultCount?: number, fileName?: string): string;
/**
* Returns a JSON-encoded value of the type:
@@ -596,6 +602,21 @@ namespace ts {
}
}
export function realizeDiagnostics(diagnostics: Diagnostic[], newLine: string): { message: string; start: number; length: number; category: string; code: number; }[] {
return diagnostics.map(d => realizeDiagnostic(d, newLine));
}
function realizeDiagnostic(diagnostic: Diagnostic, newLine: string): { message: string; start: number; length: number; category: string; code: number; } {
return {
message: flattenDiagnosticMessageText(diagnostic.messageText, newLine),
start: diagnostic.start,
length: diagnostic.length,
/// TODO: no need for the tolowerCase call
category: DiagnosticCategory[diagnostic.category].toLowerCase(),
code: diagnostic.code
};
}
class LanguageServiceShimObject extends ShimBase implements LanguageServiceShim {
private logger: Logger;
private logPerformance = false;
@@ -791,6 +812,19 @@ namespace ts {
);
}
/// GOTO Implementation
/**
* Computes the implementation location of the symbol
* at the requested position.
*/
public getImplementationAtPosition(fileName: string, position: number): string {
return this.forwardJSONCall(
`getImplementationAtPosition('${fileName}', ${position})`,
() => this.languageService.getImplementationAtPosition(fileName, position)
);
}
public getRenameInfo(fileName: string, position: number): string {
return this.forwardJSONCall(
`getRenameInfo('${fileName}', ${position})`,
@@ -923,10 +957,10 @@ namespace ts {
/// NAVIGATE TO
/** Return a list of symbols that are interesting to navigate to */
public getNavigateToItems(searchValue: string, maxResultCount?: number): string {
public getNavigateToItems(searchValue: string, maxResultCount?: number, fileName?: string): string {
return this.forwardJSONCall(
`getNavigateToItems('${searchValue}', ${maxResultCount})`,
() => this.languageService.getNavigateToItems(searchValue, maxResultCount)
`getNavigateToItems('${searchValue}', ${maxResultCount}, ${fileName})`,
() => this.languageService.getNavigateToItems(searchValue, maxResultCount, fileName)
);
}
+523
View File
@@ -0,0 +1,523 @@
/* @internal */
namespace ts.SymbolDisplay {
// TODO(drosen): use contextual SemanticMeaning.
export function getSymbolKind(typeChecker: TypeChecker, symbol: Symbol, location: Node): string {
const flags = symbol.getFlags();
if (flags & SymbolFlags.Class) return getDeclarationOfKind(symbol, SyntaxKind.ClassExpression) ?
ScriptElementKind.localClassElement : ScriptElementKind.classElement;
if (flags & SymbolFlags.Enum) return ScriptElementKind.enumElement;
if (flags & SymbolFlags.TypeAlias) return ScriptElementKind.typeElement;
if (flags & SymbolFlags.Interface) return ScriptElementKind.interfaceElement;
if (flags & SymbolFlags.TypeParameter) return ScriptElementKind.typeParameterElement;
const result = getSymbolKindOfConstructorPropertyMethodAccessorFunctionOrVar(typeChecker, symbol, flags, location);
if (result === ScriptElementKind.unknown) {
if (flags & SymbolFlags.TypeParameter) return ScriptElementKind.typeParameterElement;
if (flags & SymbolFlags.EnumMember) return ScriptElementKind.variableElement;
if (flags & SymbolFlags.Alias) return ScriptElementKind.alias;
if (flags & SymbolFlags.Module) return ScriptElementKind.moduleElement;
}
return result;
}
function getSymbolKindOfConstructorPropertyMethodAccessorFunctionOrVar(typeChecker: TypeChecker, symbol: Symbol, flags: SymbolFlags, location: Node) {
if (typeChecker.isUndefinedSymbol(symbol)) {
return ScriptElementKind.variableElement;
}
if (typeChecker.isArgumentsSymbol(symbol)) {
return ScriptElementKind.localVariableElement;
}
if (location.kind === SyntaxKind.ThisKeyword && isExpression(location)) {
return ScriptElementKind.parameterElement;
}
if (flags & SymbolFlags.Variable) {
if (isFirstDeclarationOfSymbolParameter(symbol)) {
return ScriptElementKind.parameterElement;
}
else if (symbol.valueDeclaration && isConst(symbol.valueDeclaration)) {
return ScriptElementKind.constElement;
}
else if (forEach(symbol.declarations, isLet)) {
return ScriptElementKind.letElement;
}
return isLocalVariableOrFunction(symbol) ? ScriptElementKind.localVariableElement : ScriptElementKind.variableElement;
}
if (flags & SymbolFlags.Function) return isLocalVariableOrFunction(symbol) ? ScriptElementKind.localFunctionElement : ScriptElementKind.functionElement;
if (flags & SymbolFlags.GetAccessor) return ScriptElementKind.memberGetAccessorElement;
if (flags & SymbolFlags.SetAccessor) return ScriptElementKind.memberSetAccessorElement;
if (flags & SymbolFlags.Method) return ScriptElementKind.memberFunctionElement;
if (flags & SymbolFlags.Constructor) return ScriptElementKind.constructorImplementationElement;
if (flags & SymbolFlags.Property) {
if (flags & SymbolFlags.SyntheticProperty) {
// If union property is result of union of non method (property/accessors/variables), it is labeled as property
const unionPropertyKind = forEach(typeChecker.getRootSymbols(symbol), rootSymbol => {
const rootSymbolFlags = rootSymbol.getFlags();
if (rootSymbolFlags & (SymbolFlags.PropertyOrAccessor | SymbolFlags.Variable)) {
return ScriptElementKind.memberVariableElement;
}
Debug.assert(!!(rootSymbolFlags & SymbolFlags.Method));
});
if (!unionPropertyKind) {
// If this was union of all methods,
// make sure it has call signatures before we can label it as method
const typeOfUnionProperty = typeChecker.getTypeOfSymbolAtLocation(symbol, location);
if (typeOfUnionProperty.getCallSignatures().length) {
return ScriptElementKind.memberFunctionElement;
}
return ScriptElementKind.memberVariableElement;
}
return unionPropertyKind;
}
return ScriptElementKind.memberVariableElement;
}
return ScriptElementKind.unknown;
}
export function getSymbolModifiers(symbol: Symbol): string {
return symbol && symbol.declarations && symbol.declarations.length > 0
? getNodeModifiers(symbol.declarations[0])
: ScriptElementKindModifier.none;
}
// TODO(drosen): Currently completion entry details passes the SemanticMeaning.All instead of using semanticMeaning of location
export function getSymbolDisplayPartsDocumentationAndSymbolKind(typeChecker: TypeChecker, symbol: Symbol, sourceFile: SourceFile, enclosingDeclaration: Node,
location: Node, semanticMeaning = getMeaningFromLocation(location)) {
const displayParts: SymbolDisplayPart[] = [];
let documentation: SymbolDisplayPart[];
const symbolFlags = symbol.flags;
let symbolKind = getSymbolKindOfConstructorPropertyMethodAccessorFunctionOrVar(typeChecker, symbol, symbolFlags, location);
let hasAddedSymbolInfo: boolean;
const isThisExpression = location.kind === SyntaxKind.ThisKeyword && isExpression(location);
let type: Type;
// Class at constructor site need to be shown as constructor apart from property,method, vars
if (symbolKind !== ScriptElementKind.unknown || symbolFlags & SymbolFlags.Class || symbolFlags & SymbolFlags.Alias) {
// If it is accessor they are allowed only if location is at name of the accessor
if (symbolKind === ScriptElementKind.memberGetAccessorElement || symbolKind === ScriptElementKind.memberSetAccessorElement) {
symbolKind = ScriptElementKind.memberVariableElement;
}
let signature: Signature;
type = isThisExpression ? typeChecker.getTypeAtLocation(location) : typeChecker.getTypeOfSymbolAtLocation(symbol, location);
if (type) {
if (location.parent && location.parent.kind === SyntaxKind.PropertyAccessExpression) {
const right = (<PropertyAccessExpression>location.parent).name;
// Either the location is on the right of a property access, or on the left and the right is missing
if (right === location || (right && right.getFullWidth() === 0)) {
location = location.parent;
}
}
// try get the call/construct signature from the type if it matches
let callExpression: CallExpression;
if (location.kind === SyntaxKind.CallExpression || location.kind === SyntaxKind.NewExpression) {
callExpression = <CallExpression>location;
}
else if (isCallExpressionTarget(location) || isNewExpressionTarget(location)) {
callExpression = <CallExpression>location.parent;
}
if (callExpression) {
const candidateSignatures: Signature[] = [];
signature = typeChecker.getResolvedSignature(callExpression, candidateSignatures);
if (!signature && candidateSignatures.length) {
// Use the first candidate:
signature = candidateSignatures[0];
}
const useConstructSignatures = callExpression.kind === SyntaxKind.NewExpression || callExpression.expression.kind === SyntaxKind.SuperKeyword;
const allSignatures = useConstructSignatures ? type.getConstructSignatures() : type.getCallSignatures();
if (!contains(allSignatures, signature.target) && !contains(allSignatures, signature)) {
// Get the first signature if there is one -- allSignatures may contain
// either the original signature or its target, so check for either
signature = allSignatures.length ? allSignatures[0] : undefined;
}
if (signature) {
if (useConstructSignatures && (symbolFlags & SymbolFlags.Class)) {
// Constructor
symbolKind = ScriptElementKind.constructorImplementationElement;
addPrefixForAnyFunctionOrVar(type.symbol, symbolKind);
}
else if (symbolFlags & SymbolFlags.Alias) {
symbolKind = ScriptElementKind.alias;
pushTypePart(symbolKind);
displayParts.push(spacePart());
if (useConstructSignatures) {
displayParts.push(keywordPart(SyntaxKind.NewKeyword));
displayParts.push(spacePart());
}
addFullSymbolName(symbol);
}
else {
addPrefixForAnyFunctionOrVar(symbol, symbolKind);
}
switch (symbolKind) {
case ScriptElementKind.memberVariableElement:
case ScriptElementKind.variableElement:
case ScriptElementKind.constElement:
case ScriptElementKind.letElement:
case ScriptElementKind.parameterElement:
case ScriptElementKind.localVariableElement:
// If it is call or construct signature of lambda's write type name
displayParts.push(punctuationPart(SyntaxKind.ColonToken));
displayParts.push(spacePart());
if (useConstructSignatures) {
displayParts.push(keywordPart(SyntaxKind.NewKeyword));
displayParts.push(spacePart());
}
if (!(type.flags & TypeFlags.Anonymous) && type.symbol) {
addRange(displayParts, symbolToDisplayParts(typeChecker, type.symbol, enclosingDeclaration, /*meaning*/ undefined, SymbolFormatFlags.WriteTypeParametersOrArguments));
}
addSignatureDisplayParts(signature, allSignatures, TypeFormatFlags.WriteArrowStyleSignature);
break;
default:
// Just signature
addSignatureDisplayParts(signature, allSignatures);
}
hasAddedSymbolInfo = true;
}
}
else if ((isNameOfFunctionDeclaration(location) && !(symbol.flags & SymbolFlags.Accessor)) || // name of function declaration
(location.kind === SyntaxKind.ConstructorKeyword && location.parent.kind === SyntaxKind.Constructor)) { // At constructor keyword of constructor declaration
// get the signature from the declaration and write it
const functionDeclaration = <FunctionLikeDeclaration>location.parent;
const allSignatures = functionDeclaration.kind === SyntaxKind.Constructor ? type.getNonNullableType().getConstructSignatures() : type.getNonNullableType().getCallSignatures();
if (!typeChecker.isImplementationOfOverload(functionDeclaration)) {
signature = typeChecker.getSignatureFromDeclaration(functionDeclaration);
}
else {
signature = allSignatures[0];
}
if (functionDeclaration.kind === SyntaxKind.Constructor) {
// show (constructor) Type(...) signature
symbolKind = ScriptElementKind.constructorImplementationElement;
addPrefixForAnyFunctionOrVar(type.symbol, symbolKind);
}
else {
// (function/method) symbol(..signature)
addPrefixForAnyFunctionOrVar(functionDeclaration.kind === SyntaxKind.CallSignature &&
!(type.symbol.flags & SymbolFlags.TypeLiteral || type.symbol.flags & SymbolFlags.ObjectLiteral) ? type.symbol : symbol, symbolKind);
}
addSignatureDisplayParts(signature, allSignatures);
hasAddedSymbolInfo = true;
}
}
}
if (symbolFlags & SymbolFlags.Class && !hasAddedSymbolInfo && !isThisExpression) {
if (getDeclarationOfKind(symbol, SyntaxKind.ClassExpression)) {
// Special case for class expressions because we would like to indicate that
// the class name is local to the class body (similar to function expression)
// (local class) class <className>
pushTypePart(ScriptElementKind.localClassElement);
}
else {
// Class declaration has name which is not local.
displayParts.push(keywordPart(SyntaxKind.ClassKeyword));
}
displayParts.push(spacePart());
addFullSymbolName(symbol);
writeTypeParametersOfSymbol(symbol, sourceFile);
}
if ((symbolFlags & SymbolFlags.Interface) && (semanticMeaning & SemanticMeaning.Type)) {
addNewLineIfDisplayPartsExist();
displayParts.push(keywordPart(SyntaxKind.InterfaceKeyword));
displayParts.push(spacePart());
addFullSymbolName(symbol);
writeTypeParametersOfSymbol(symbol, sourceFile);
}
if (symbolFlags & SymbolFlags.TypeAlias) {
addNewLineIfDisplayPartsExist();
displayParts.push(keywordPart(SyntaxKind.TypeKeyword));
displayParts.push(spacePart());
addFullSymbolName(symbol);
writeTypeParametersOfSymbol(symbol, sourceFile);
displayParts.push(spacePart());
displayParts.push(operatorPart(SyntaxKind.EqualsToken));
displayParts.push(spacePart());
addRange(displayParts, typeToDisplayParts(typeChecker, typeChecker.getDeclaredTypeOfSymbol(symbol), enclosingDeclaration, TypeFormatFlags.InTypeAlias));
}
if (symbolFlags & SymbolFlags.Enum) {
addNewLineIfDisplayPartsExist();
if (forEach(symbol.declarations, isConstEnumDeclaration)) {
displayParts.push(keywordPart(SyntaxKind.ConstKeyword));
displayParts.push(spacePart());
}
displayParts.push(keywordPart(SyntaxKind.EnumKeyword));
displayParts.push(spacePart());
addFullSymbolName(symbol);
}
if (symbolFlags & SymbolFlags.Module) {
addNewLineIfDisplayPartsExist();
const declaration = <ModuleDeclaration>getDeclarationOfKind(symbol, SyntaxKind.ModuleDeclaration);
const isNamespace = declaration && declaration.name && declaration.name.kind === SyntaxKind.Identifier;
displayParts.push(keywordPart(isNamespace ? SyntaxKind.NamespaceKeyword : SyntaxKind.ModuleKeyword));
displayParts.push(spacePart());
addFullSymbolName(symbol);
}
if ((symbolFlags & SymbolFlags.TypeParameter) && (semanticMeaning & SemanticMeaning.Type)) {
addNewLineIfDisplayPartsExist();
displayParts.push(punctuationPart(SyntaxKind.OpenParenToken));
displayParts.push(textPart("type parameter"));
displayParts.push(punctuationPart(SyntaxKind.CloseParenToken));
displayParts.push(spacePart());
addFullSymbolName(symbol);
displayParts.push(spacePart());
displayParts.push(keywordPart(SyntaxKind.InKeyword));
displayParts.push(spacePart());
if (symbol.parent) {
// Class/Interface type parameter
addFullSymbolName(symbol.parent, enclosingDeclaration);
writeTypeParametersOfSymbol(symbol.parent, enclosingDeclaration);
}
else {
// Method/function type parameter
let declaration = <Node>getDeclarationOfKind(symbol, SyntaxKind.TypeParameter);
Debug.assert(declaration !== undefined);
declaration = declaration.parent;
if (declaration) {
if (isFunctionLikeKind(declaration.kind)) {
const signature = typeChecker.getSignatureFromDeclaration(<SignatureDeclaration>declaration);
if (declaration.kind === SyntaxKind.ConstructSignature) {
displayParts.push(keywordPart(SyntaxKind.NewKeyword));
displayParts.push(spacePart());
}
else if (declaration.kind !== SyntaxKind.CallSignature && (<SignatureDeclaration>declaration).name) {
addFullSymbolName(declaration.symbol);
}
addRange(displayParts, signatureToDisplayParts(typeChecker, signature, sourceFile, TypeFormatFlags.WriteTypeArgumentsOfSignature));
}
else {
// Type alias type parameter
// For example
// type list<T> = T[]; // Both T will go through same code path
displayParts.push(keywordPart(SyntaxKind.TypeKeyword));
displayParts.push(spacePart());
addFullSymbolName(declaration.symbol);
writeTypeParametersOfSymbol(declaration.symbol, sourceFile);
}
}
}
}
if (symbolFlags & SymbolFlags.EnumMember) {
addPrefixForAnyFunctionOrVar(symbol, "enum member");
const declaration = symbol.declarations[0];
if (declaration.kind === SyntaxKind.EnumMember) {
const constantValue = typeChecker.getConstantValue(<EnumMember>declaration);
if (constantValue !== undefined) {
displayParts.push(spacePart());
displayParts.push(operatorPart(SyntaxKind.EqualsToken));
displayParts.push(spacePart());
displayParts.push(displayPart(constantValue.toString(), SymbolDisplayPartKind.numericLiteral));
}
}
}
if (symbolFlags & SymbolFlags.Alias) {
addNewLineIfDisplayPartsExist();
if (symbol.declarations[0].kind === SyntaxKind.NamespaceExportDeclaration) {
displayParts.push(keywordPart(SyntaxKind.ExportKeyword));
displayParts.push(spacePart());
displayParts.push(keywordPart(SyntaxKind.NamespaceKeyword));
}
else {
displayParts.push(keywordPart(SyntaxKind.ImportKeyword));
}
displayParts.push(spacePart());
addFullSymbolName(symbol);
ts.forEach(symbol.declarations, declaration => {
if (declaration.kind === SyntaxKind.ImportEqualsDeclaration) {
const importEqualsDeclaration = <ImportEqualsDeclaration>declaration;
if (isExternalModuleImportEqualsDeclaration(importEqualsDeclaration)) {
displayParts.push(spacePart());
displayParts.push(operatorPart(SyntaxKind.EqualsToken));
displayParts.push(spacePart());
displayParts.push(keywordPart(SyntaxKind.RequireKeyword));
displayParts.push(punctuationPart(SyntaxKind.OpenParenToken));
displayParts.push(displayPart(getTextOfNode(getExternalModuleImportEqualsDeclarationExpression(importEqualsDeclaration)), SymbolDisplayPartKind.stringLiteral));
displayParts.push(punctuationPart(SyntaxKind.CloseParenToken));
}
else {
const internalAliasSymbol = typeChecker.getSymbolAtLocation(importEqualsDeclaration.moduleReference);
if (internalAliasSymbol) {
displayParts.push(spacePart());
displayParts.push(operatorPart(SyntaxKind.EqualsToken));
displayParts.push(spacePart());
addFullSymbolName(internalAliasSymbol, enclosingDeclaration);
}
}
return true;
}
});
}
if (!hasAddedSymbolInfo) {
if (symbolKind !== ScriptElementKind.unknown) {
if (type) {
if (isThisExpression) {
addNewLineIfDisplayPartsExist();
displayParts.push(keywordPart(SyntaxKind.ThisKeyword));
}
else {
addPrefixForAnyFunctionOrVar(symbol, symbolKind);
}
// For properties, variables and local vars: show the type
if (symbolKind === ScriptElementKind.memberVariableElement ||
symbolFlags & SymbolFlags.Variable ||
symbolKind === ScriptElementKind.localVariableElement ||
isThisExpression) {
displayParts.push(punctuationPart(SyntaxKind.ColonToken));
displayParts.push(spacePart());
// If the type is type parameter, format it specially
if (type.symbol && type.symbol.flags & SymbolFlags.TypeParameter) {
const typeParameterParts = mapToDisplayParts(writer => {
typeChecker.getSymbolDisplayBuilder().buildTypeParameterDisplay(<TypeParameter>type, writer, enclosingDeclaration);
});
addRange(displayParts, typeParameterParts);
}
else {
addRange(displayParts, typeToDisplayParts(typeChecker, type, enclosingDeclaration));
}
}
else if (symbolFlags & SymbolFlags.Function ||
symbolFlags & SymbolFlags.Method ||
symbolFlags & SymbolFlags.Constructor ||
symbolFlags & SymbolFlags.Signature ||
symbolFlags & SymbolFlags.Accessor ||
symbolKind === ScriptElementKind.memberFunctionElement) {
const allSignatures = type.getNonNullableType().getCallSignatures();
addSignatureDisplayParts(allSignatures[0], allSignatures);
}
}
}
else {
symbolKind = getSymbolKind(typeChecker, symbol, location);
}
}
if (!documentation) {
documentation = symbol.getDocumentationComment();
if (documentation.length === 0 && symbol.flags & SymbolFlags.Property) {
// For some special property access expressions like `experts.foo = foo` or `module.exports.foo = foo`
// there documentation comments might be attached to the right hand side symbol of their declarations.
// The pattern of such special property access is that the parent symbol is the symbol of the file.
if (symbol.parent && forEach(symbol.parent.declarations, declaration => declaration.kind === SyntaxKind.SourceFile)) {
for (const declaration of symbol.declarations) {
if (!declaration.parent || declaration.parent.kind !== SyntaxKind.BinaryExpression) {
continue;
}
const rhsSymbol = typeChecker.getSymbolAtLocation((<BinaryExpression>declaration.parent).right);
if (!rhsSymbol) {
continue;
}
documentation = rhsSymbol.getDocumentationComment();
if (documentation.length > 0) {
break;
}
}
}
}
}
return { displayParts, documentation, symbolKind };
function addNewLineIfDisplayPartsExist() {
if (displayParts.length) {
displayParts.push(lineBreakPart());
}
}
function addFullSymbolName(symbol: Symbol, enclosingDeclaration?: Node) {
const fullSymbolDisplayParts = symbolToDisplayParts(typeChecker, symbol, enclosingDeclaration || sourceFile, /*meaning*/ undefined,
SymbolFormatFlags.WriteTypeParametersOrArguments | SymbolFormatFlags.UseOnlyExternalAliasing);
addRange(displayParts, fullSymbolDisplayParts);
}
function addPrefixForAnyFunctionOrVar(symbol: Symbol, symbolKind: string) {
addNewLineIfDisplayPartsExist();
if (symbolKind) {
pushTypePart(symbolKind);
displayParts.push(spacePart());
addFullSymbolName(symbol);
}
}
function pushTypePart(symbolKind: string) {
switch (symbolKind) {
case ScriptElementKind.variableElement:
case ScriptElementKind.functionElement:
case ScriptElementKind.letElement:
case ScriptElementKind.constElement:
case ScriptElementKind.constructorImplementationElement:
displayParts.push(textOrKeywordPart(symbolKind));
return;
default:
displayParts.push(punctuationPart(SyntaxKind.OpenParenToken));
displayParts.push(textOrKeywordPart(symbolKind));
displayParts.push(punctuationPart(SyntaxKind.CloseParenToken));
return;
}
}
function addSignatureDisplayParts(signature: Signature, allSignatures: Signature[], flags?: TypeFormatFlags) {
addRange(displayParts, signatureToDisplayParts(typeChecker, signature, enclosingDeclaration, flags | TypeFormatFlags.WriteTypeArgumentsOfSignature));
if (allSignatures.length > 1) {
displayParts.push(spacePart());
displayParts.push(punctuationPart(SyntaxKind.OpenParenToken));
displayParts.push(operatorPart(SyntaxKind.PlusToken));
displayParts.push(displayPart((allSignatures.length - 1).toString(), SymbolDisplayPartKind.numericLiteral));
displayParts.push(spacePart());
displayParts.push(textPart(allSignatures.length === 2 ? "overload" : "overloads"));
displayParts.push(punctuationPart(SyntaxKind.CloseParenToken));
}
documentation = signature.getDocumentationComment();
}
function writeTypeParametersOfSymbol(symbol: Symbol, enclosingDeclaration: Node) {
const typeParameterParts = mapToDisplayParts(writer => {
typeChecker.getSymbolDisplayBuilder().buildTypeParameterDisplayFromSymbol(symbol, writer, enclosingDeclaration);
});
addRange(displayParts, typeParameterParts);
}
}
function isLocalVariableOrFunction(symbol: Symbol) {
if (symbol.parent) {
return false; // This is exported symbol
}
return ts.forEach(symbol.declarations, declaration => {
// Function expressions are local
if (declaration.kind === SyntaxKind.FunctionExpression) {
return true;
}
if (declaration.kind !== SyntaxKind.VariableDeclaration && declaration.kind !== SyntaxKind.FunctionDeclaration) {
return false;
}
// If the parent is not sourceFile or module block it is local variable
for (let parent = declaration.parent; !isFunctionBlock(parent); parent = parent.parent) {
// Reached source file or module block
if (parent.kind === SyntaxKind.SourceFile || parent.kind === SyntaxKind.ModuleBlock) {
return false;
}
}
// parent is in function block
return true;
});
}
}
+154
View File
@@ -0,0 +1,154 @@
namespace ts {
export interface TranspileOptions {
compilerOptions?: CompilerOptions;
fileName?: string;
reportDiagnostics?: boolean;
moduleName?: string;
renamedDependencies?: MapLike<string>;
}
export interface TranspileOutput {
outputText: string;
diagnostics?: Diagnostic[];
sourceMapText?: string;
}
/*
* This function will compile source text from 'input' argument using specified compiler options.
* If not options are provided - it will use a set of default compiler options.
* Extra compiler options that will unconditionally be used by this function are:
* - isolatedModules = true
* - allowNonTsExtensions = true
* - noLib = true
* - noResolve = true
*/
export function transpileModule(input: string, transpileOptions: TranspileOptions): TranspileOutput {
const diagnostics: Diagnostic[] = [];
const options: CompilerOptions = transpileOptions.compilerOptions ? fixupCompilerOptions(transpileOptions.compilerOptions, diagnostics) : getDefaultCompilerOptions();
options.isolatedModules = true;
// transpileModule does not write anything to disk so there is no need to verify that there are no conflicts between input and output paths.
options.suppressOutputPathCheck = true;
// Filename can be non-ts file.
options.allowNonTsExtensions = true;
// We are not returning a sourceFile for lib file when asked by the program,
// so pass --noLib to avoid reporting a file not found error.
options.noLib = true;
// Clear out other settings that would not be used in transpiling this module
options.lib = undefined;
options.types = undefined;
options.noEmit = undefined;
options.noEmitOnError = undefined;
options.paths = undefined;
options.rootDirs = undefined;
options.declaration = undefined;
options.declarationDir = undefined;
options.out = undefined;
options.outFile = undefined;
// We are not doing a full typecheck, we are not resolving the whole context,
// so pass --noResolve to avoid reporting missing file errors.
options.noResolve = true;
// if jsx is specified then treat file as .tsx
const inputFileName = transpileOptions.fileName || (options.jsx ? "module.tsx" : "module.ts");
const sourceFile = createSourceFile(inputFileName, input, options.target);
if (transpileOptions.moduleName) {
sourceFile.moduleName = transpileOptions.moduleName;
}
if (transpileOptions.renamedDependencies) {
sourceFile.renamedDependencies = createMap(transpileOptions.renamedDependencies);
}
const newLine = getNewLineCharacter(options);
// Output
let outputText: string;
let sourceMapText: string;
// Create a compilerHost object to allow the compiler to read and write files
const compilerHost: CompilerHost = {
getSourceFile: (fileName, target) => fileName === normalizePath(inputFileName) ? sourceFile : undefined,
writeFile: (name, text, writeByteOrderMark) => {
if (fileExtensionIs(name, ".map")) {
Debug.assert(sourceMapText === undefined, `Unexpected multiple source map outputs for the file '${name}'`);
sourceMapText = text;
}
else {
Debug.assert(outputText === undefined, `Unexpected multiple outputs for the file: '${name}'`);
outputText = text;
}
},
getDefaultLibFileName: () => "lib.d.ts",
useCaseSensitiveFileNames: () => false,
getCanonicalFileName: fileName => fileName,
getCurrentDirectory: () => "",
getNewLine: () => newLine,
fileExists: (fileName): boolean => fileName === inputFileName,
readFile: (fileName): string => "",
directoryExists: directoryExists => true,
getDirectories: (path: string) => []
};
const program = createProgram([inputFileName], options, compilerHost);
if (transpileOptions.reportDiagnostics) {
addRange(/*to*/ diagnostics, /*from*/ program.getSyntacticDiagnostics(sourceFile));
addRange(/*to*/ diagnostics, /*from*/ program.getOptionsDiagnostics());
}
// Emit
program.emit();
Debug.assert(outputText !== undefined, "Output generation failed");
return { outputText, diagnostics, sourceMapText };
}
/*
* This is a shortcut function for transpileModule - it accepts transpileOptions as parameters and returns only outputText part of the result.
*/
export function transpile(input: string, compilerOptions?: CompilerOptions, fileName?: string, diagnostics?: Diagnostic[], moduleName?: string): string {
const output = transpileModule(input, { compilerOptions, fileName, reportDiagnostics: !!diagnostics, moduleName });
// addRange correctly handles cases when wither 'from' or 'to' argument is missing
addRange(diagnostics, output.diagnostics);
return output.outputText;
}
let commandLineOptionsStringToEnum: CommandLineOptionOfCustomType[];
/** JS users may pass in string values for enum compiler options (such as ModuleKind), so convert. */
function fixupCompilerOptions(options: CompilerOptions, diagnostics: Diagnostic[]): CompilerOptions {
// Lazily create this value to fix module loading errors.
commandLineOptionsStringToEnum = commandLineOptionsStringToEnum || <CommandLineOptionOfCustomType[]>filter(optionDeclarations, o =>
typeof o.type === "object" && !forEachProperty(o.type, v => typeof v !== "number"));
options = clone(options);
for (const opt of commandLineOptionsStringToEnum) {
if (!hasProperty(options, opt.name)) {
continue;
}
const value = options[opt.name];
// Value should be a key of opt.type
if (typeof value === "string") {
// If value is not a string, this will fail
options[opt.name] = parseCustomTypeOption(opt, value, diagnostics);
}
else {
if (!forEachProperty(opt.type, v => v === value)) {
// Supplied value isn't a valid enum value.
diagnostics.push(createCompilerDiagnosticForInvalidCustomType(opt));
}
}
}
return options;
}
}
+28 -2
View File
@@ -21,22 +21,48 @@
"../compiler/utilities.ts",
"../compiler/binder.ts",
"../compiler/checker.ts",
"../compiler/factory.ts",
"../compiler/visitor.ts",
"../compiler/transformers/ts.ts",
"../compiler/transformers/jsx.ts",
"../compiler/transformers/es7.ts",
"../compiler/transformers/es6.ts",
"../compiler/transformers/generators.ts",
"../compiler/transformers/destructuring.ts",
"../compiler/transformers/module/module.ts",
"../compiler/transformers/module/system.ts",
"../compiler/transformers/module/es6.ts",
"../compiler/transformer.ts",
"../compiler/comments.ts",
"../compiler/sourcemap.ts",
"../compiler/declarationEmitter.ts",
"../compiler/emitter.ts",
"../compiler/program.ts",
"../compiler/commandLineParser.ts",
"../compiler/diagnosticInformationMap.generated.ts",
"types.ts",
"utilities.ts",
"breakpoints.ts",
"classifier.ts",
"completions.ts",
"documentHighlights.ts",
"documentRegistry.ts",
"findAllReferences.ts",
"goToDefinition.ts",
"goToImplementation.ts",
"jsDoc.ts",
"jsTyping.ts",
"navigateTo.ts",
"navigationBar.ts",
"outliningElementsCollector.ts",
"patternMatcher.ts",
"preProcess.ts",
"rename.ts",
"services.ts",
"transpile.ts",
"shims.ts",
"signatureHelp.ts",
"utilities.ts",
"jsTyping.ts",
"symbolDisplay.ts",
"formatting/formatting.ts",
"formatting/formattingContext.ts",
"formatting/formattingRequestKind.ts",
+777
View File
@@ -0,0 +1,777 @@
namespace ts {
export interface Node {
getSourceFile(): SourceFile;
getChildCount(sourceFile?: SourceFile): number;
getChildAt(index: number, sourceFile?: SourceFile): Node;
getChildren(sourceFile?: SourceFile): Node[];
getStart(sourceFile?: SourceFile, includeJsDocComment?: boolean): number;
getFullStart(): number;
getEnd(): number;
getWidth(sourceFile?: SourceFile): number;
getFullWidth(): number;
getLeadingTriviaWidth(sourceFile?: SourceFile): number;
getFullText(sourceFile?: SourceFile): string;
getText(sourceFile?: SourceFile): string;
getFirstToken(sourceFile?: SourceFile): Node;
getLastToken(sourceFile?: SourceFile): Node;
}
export interface Symbol {
getFlags(): SymbolFlags;
getName(): string;
getDeclarations(): Declaration[];
getDocumentationComment(): SymbolDisplayPart[];
}
export interface Type {
getFlags(): TypeFlags;
getSymbol(): Symbol;
getProperties(): Symbol[];
getProperty(propertyName: string): Symbol;
getApparentProperties(): Symbol[];
getCallSignatures(): Signature[];
getConstructSignatures(): Signature[];
getStringIndexType(): Type;
getNumberIndexType(): Type;
getBaseTypes(): ObjectType[];
getNonNullableType(): Type;
}
export interface Signature {
getDeclaration(): SignatureDeclaration;
getTypeParameters(): Type[];
getParameters(): Symbol[];
getReturnType(): Type;
getDocumentationComment(): SymbolDisplayPart[];
}
export interface SourceFile {
/* @internal */ version: string;
/* @internal */ scriptSnapshot: IScriptSnapshot;
/* @internal */ nameTable: Map<number>;
/* @internal */ getNamedDeclarations(): Map<Declaration[]>;
getLineAndCharacterOfPosition(pos: number): LineAndCharacter;
getLineStarts(): number[];
getPositionOfLineAndCharacter(line: number, character: number): number;
update(newText: string, textChangeRange: TextChangeRange): SourceFile;
}
/**
* Represents an immutable snapshot of a script at a specified time.Once acquired, the
* snapshot is observably immutable. i.e. the same calls with the same parameters will return
* the same values.
*/
export interface IScriptSnapshot {
/** Gets a portion of the script snapshot specified by [start, end). */
getText(start: number, end: number): string;
/** Gets the length of this script snapshot. */
getLength(): number;
/**
* Gets the TextChangeRange that describe how the text changed between this text and
* an older version. This information is used by the incremental parser to determine
* what sections of the script need to be re-parsed. 'undefined' can be returned if the
* change range cannot be determined. However, in that case, incremental parsing will
* not happen and the entire document will be re - parsed.
*/
getChangeRange(oldSnapshot: IScriptSnapshot): TextChangeRange | undefined;
/** Releases all resources held by this script snapshot */
dispose?(): void;
}
export namespace ScriptSnapshot {
class StringScriptSnapshot implements IScriptSnapshot {
constructor(private text: string) {
}
public getText(start: number, end: number): string {
return this.text.substring(start, end);
}
public getLength(): number {
return this.text.length;
}
public getChangeRange(oldSnapshot: IScriptSnapshot): TextChangeRange {
// Text-based snapshots do not support incremental parsing. Return undefined
// to signal that to the caller.
return undefined;
}
}
export function fromString(text: string): IScriptSnapshot {
return new StringScriptSnapshot(text);
}
}
export interface PreProcessedFileInfo {
referencedFiles: FileReference[];
typeReferenceDirectives: FileReference[];
importedFiles: FileReference[];
ambientExternalModules: string[];
isLibFile: boolean;
}
export interface HostCancellationToken {
isCancellationRequested(): boolean;
}
//
// Public interface of the host of a language service instance.
//
export interface LanguageServiceHost {
getCompilationSettings(): CompilerOptions;
getNewLine?(): string;
getProjectVersion?(): string;
getScriptFileNames(): string[];
getScriptKind?(fileName: string): ScriptKind;
getScriptVersion(fileName: string): string;
getScriptSnapshot(fileName: string): IScriptSnapshot | undefined;
getLocalizedDiagnosticMessages?(): any;
getCancellationToken?(): HostCancellationToken;
getCurrentDirectory(): string;
getDefaultLibFileName(options: CompilerOptions): string;
log?(s: string): void;
trace?(s: string): void;
error?(s: string): void;
useCaseSensitiveFileNames?(): boolean;
/*
* LS host can optionally implement these methods to support completions for module specifiers.
* Without these methods, only completions for ambient modules will be provided.
*/
readDirectory?(path: string, extensions?: string[], exclude?: string[], include?: string[]): string[];
readFile?(path: string, encoding?: string): string;
fileExists?(path: string): boolean;
/*
* LS host can optionally implement this method if it wants to be completely in charge of module name resolution.
* if implementation is omitted then language service will use built-in module resolution logic and get answers to
* host specific questions using 'getScriptSnapshot'.
*/
resolveModuleNames?(moduleNames: string[], containingFile: string): ResolvedModule[];
resolveTypeReferenceDirectives?(typeDirectiveNames: string[], containingFile: string): ResolvedTypeReferenceDirective[];
directoryExists?(directoryName: string): boolean;
/*
* getDirectories is also required for full import and type reference completions. Without it defined, certain
* completions will not be provided
*/
getDirectories?(directoryName: string): string[];
}
//
// Public services of a language service instance associated
// with a language service host instance
//
export interface LanguageService {
cleanupSemanticCache(): void;
getSyntacticDiagnostics(fileName: string): Diagnostic[];
getSemanticDiagnostics(fileName: string): Diagnostic[];
// TODO: Rename this to getProgramDiagnostics to better indicate that these are any
// diagnostics present for the program level, and not just 'options' diagnostics.
getCompilerOptionsDiagnostics(): Diagnostic[];
/**
* @deprecated Use getEncodedSyntacticClassifications instead.
*/
getSyntacticClassifications(fileName: string, span: TextSpan): ClassifiedSpan[];
/**
* @deprecated Use getEncodedSemanticClassifications instead.
*/
getSemanticClassifications(fileName: string, span: TextSpan): ClassifiedSpan[];
// Encoded as triples of [start, length, ClassificationType].
getEncodedSyntacticClassifications(fileName: string, span: TextSpan): Classifications;
getEncodedSemanticClassifications(fileName: string, span: TextSpan): Classifications;
getCompletionsAtPosition(fileName: string, position: number): CompletionInfo;
getCompletionEntryDetails(fileName: string, position: number, entryName: string): CompletionEntryDetails;
getCompletionEntrySymbol(fileName: string, position: number, entryName: string): Symbol;
getQuickInfoAtPosition(fileName: string, position: number): QuickInfo;
getNameOrDottedNameSpan(fileName: string, startPos: number, endPos: number): TextSpan;
getBreakpointStatementAtPosition(fileName: string, position: number): TextSpan;
getSignatureHelpItems(fileName: string, position: number): SignatureHelpItems;
getRenameInfo(fileName: string, position: number): RenameInfo;
findRenameLocations(fileName: string, position: number, findInStrings: boolean, findInComments: boolean): RenameLocation[];
getDefinitionAtPosition(fileName: string, position: number): DefinitionInfo[];
getTypeDefinitionAtPosition(fileName: string, position: number): DefinitionInfo[];
getImplementationAtPosition(fileName: string, position: number): ImplementationLocation[];
getReferencesAtPosition(fileName: string, position: number): ReferenceEntry[];
findReferences(fileName: string, position: number): ReferencedSymbol[];
getDocumentHighlights(fileName: string, position: number, filesToSearch: string[]): DocumentHighlights[];
/** @deprecated */
getOccurrencesAtPosition(fileName: string, position: number): ReferenceEntry[];
getNavigateToItems(searchValue: string, maxResultCount?: number, fileName?: string, excludeDtsFiles?: boolean): NavigateToItem[];
getNavigationBarItems(fileName: string): NavigationBarItem[];
getOutliningSpans(fileName: string): OutliningSpan[];
getTodoComments(fileName: string, descriptors: TodoCommentDescriptor[]): TodoComment[];
getBraceMatchingAtPosition(fileName: string, position: number): TextSpan[];
getIndentationAtPosition(fileName: string, position: number, options: EditorOptions | EditorSettings): number;
getFormattingEditsForRange(fileName: string, start: number, end: number, options: FormatCodeOptions | FormatCodeSettings): TextChange[];
getFormattingEditsForDocument(fileName: string, options: FormatCodeOptions | FormatCodeSettings): TextChange[];
getFormattingEditsAfterKeystroke(fileName: string, position: number, key: string, options: FormatCodeOptions | FormatCodeSettings): TextChange[];
getDocCommentTemplateAtPosition(fileName: string, position: number): TextInsertion;
isValidBraceCompletionAtPosition(fileName: string, position: number, openingBrace: number): boolean;
getEmitOutput(fileName: string, emitOnlyDtsFiles?: boolean): EmitOutput;
getProgram(): Program;
/* @internal */ getNonBoundSourceFile(fileName: string): SourceFile;
/**
* @internal
* @deprecated Use ts.createSourceFile instead.
*/
getSourceFile(fileName: string): SourceFile;
dispose(): void;
}
export interface Classifications {
spans: number[];
endOfLineState: EndOfLineState;
}
export interface ClassifiedSpan {
textSpan: TextSpan;
classificationType: string; // ClassificationTypeNames
}
export interface NavigationBarItem {
text: string;
kind: string;
kindModifiers: string;
spans: TextSpan[];
childItems: NavigationBarItem[];
indent: number;
bolded: boolean;
grayed: boolean;
}
export interface TodoCommentDescriptor {
text: string;
priority: number;
}
export interface TodoComment {
descriptor: TodoCommentDescriptor;
message: string;
position: number;
}
export class TextChange {
span: TextSpan;
newText: string;
}
export interface TextInsertion {
newText: string;
/** The position in newText the caret should point to after the insertion. */
caretOffset: number;
}
export interface RenameLocation {
textSpan: TextSpan;
fileName: string;
}
export interface ReferenceEntry {
textSpan: TextSpan;
fileName: string;
isWriteAccess: boolean;
isDefinition: boolean;
}
export interface ImplementationLocation {
textSpan: TextSpan;
fileName: string;
}
export interface DocumentHighlights {
fileName: string;
highlightSpans: HighlightSpan[];
}
export namespace HighlightSpanKind {
export const none = "none";
export const definition = "definition";
export const reference = "reference";
export const writtenReference = "writtenReference";
}
export interface HighlightSpan {
fileName?: string;
textSpan: TextSpan;
kind: string;
}
export interface NavigateToItem {
name: string;
kind: string;
kindModifiers: string;
matchKind: string;
isCaseSensitive: boolean;
fileName: string;
textSpan: TextSpan;
containerName: string;
containerKind: string;
}
export enum IndentStyle {
None = 0,
Block = 1,
Smart = 2,
}
/* @deprecated - consider using EditorSettings instead */
export interface EditorOptions {
BaseIndentSize?: number;
IndentSize: number;
TabSize: number;
NewLineCharacter: string;
ConvertTabsToSpaces: boolean;
IndentStyle: IndentStyle;
}
export interface EditorSettings {
baseIndentSize?: number;
indentSize: number;
tabSize: number;
newLineCharacter: string;
convertTabsToSpaces: boolean;
indentStyle: IndentStyle;
}
/* @deprecated - consider using FormatCodeSettings instead */
export interface FormatCodeOptions extends EditorOptions {
InsertSpaceAfterCommaDelimiter: boolean;
InsertSpaceAfterSemicolonInForStatements: boolean;
InsertSpaceBeforeAndAfterBinaryOperators: boolean;
InsertSpaceAfterKeywordsInControlFlowStatements: boolean;
InsertSpaceAfterFunctionKeywordForAnonymousFunctions: boolean;
InsertSpaceAfterOpeningAndBeforeClosingNonemptyParenthesis: boolean;
InsertSpaceAfterOpeningAndBeforeClosingNonemptyBrackets: boolean;
InsertSpaceAfterOpeningAndBeforeClosingNonemptyBraces?: boolean;
InsertSpaceAfterOpeningAndBeforeClosingTemplateStringBraces: boolean;
InsertSpaceAfterOpeningAndBeforeClosingJsxExpressionBraces?: boolean;
InsertSpaceAfterTypeAssertion?: boolean;
PlaceOpenBraceOnNewLineForFunctions: boolean;
PlaceOpenBraceOnNewLineForControlBlocks: boolean;
}
export interface FormatCodeSettings extends EditorSettings {
insertSpaceAfterCommaDelimiter: boolean;
insertSpaceAfterSemicolonInForStatements: boolean;
insertSpaceBeforeAndAfterBinaryOperators: boolean;
insertSpaceAfterKeywordsInControlFlowStatements: boolean;
insertSpaceAfterFunctionKeywordForAnonymousFunctions: boolean;
insertSpaceAfterOpeningAndBeforeClosingNonemptyParenthesis: boolean;
insertSpaceAfterOpeningAndBeforeClosingNonemptyBrackets: boolean;
insertSpaceAfterOpeningAndBeforeClosingNonemptyBraces?: boolean;
insertSpaceAfterOpeningAndBeforeClosingTemplateStringBraces: boolean;
insertSpaceAfterOpeningAndBeforeClosingJsxExpressionBraces: boolean;
insertSpaceAfterTypeAssertion?: boolean;
placeOpenBraceOnNewLineForFunctions: boolean;
placeOpenBraceOnNewLineForControlBlocks: boolean;
}
export interface DefinitionInfo {
fileName: string;
textSpan: TextSpan;
kind: string;
name: string;
containerKind: string;
containerName: string;
}
export interface ReferencedSymbolDefinitionInfo extends DefinitionInfo {
displayParts: SymbolDisplayPart[];
}
export interface ReferencedSymbol {
definition: ReferencedSymbolDefinitionInfo;
references: ReferenceEntry[];
}
export enum SymbolDisplayPartKind {
aliasName,
className,
enumName,
fieldName,
interfaceName,
keyword,
lineBreak,
numericLiteral,
stringLiteral,
localName,
methodName,
moduleName,
operator,
parameterName,
propertyName,
punctuation,
space,
text,
typeParameterName,
enumMemberName,
functionName,
regularExpressionLiteral,
}
export interface SymbolDisplayPart {
text: string;
kind: string;
}
export interface QuickInfo {
kind: string;
kindModifiers: string;
textSpan: TextSpan;
displayParts: SymbolDisplayPart[];
documentation: SymbolDisplayPart[];
}
export interface RenameInfo {
canRename: boolean;
localizedErrorMessage: string;
displayName: string;
fullDisplayName: string;
kind: string;
kindModifiers: string;
triggerSpan: TextSpan;
}
export interface SignatureHelpParameter {
name: string;
documentation: SymbolDisplayPart[];
displayParts: SymbolDisplayPart[];
isOptional: boolean;
}
/**
* Represents a single signature to show in signature help.
* The id is used for subsequent calls into the language service to ask questions about the
* signature help item in the context of any documents that have been updated. i.e. after
* an edit has happened, while signature help is still active, the host can ask important
* questions like 'what parameter is the user currently contained within?'.
*/
export interface SignatureHelpItem {
isVariadic: boolean;
prefixDisplayParts: SymbolDisplayPart[];
suffixDisplayParts: SymbolDisplayPart[];
separatorDisplayParts: SymbolDisplayPart[];
parameters: SignatureHelpParameter[];
documentation: SymbolDisplayPart[];
}
/**
* Represents a set of signature help items, and the preferred item that should be selected.
*/
export interface SignatureHelpItems {
items: SignatureHelpItem[];
applicableSpan: TextSpan;
selectedItemIndex: number;
argumentIndex: number;
argumentCount: number;
}
export interface CompletionInfo {
isMemberCompletion: boolean;
isNewIdentifierLocation: boolean; // true when the current location also allows for a new identifier
entries: CompletionEntry[];
}
export interface CompletionEntry {
name: string;
kind: string; // see ScriptElementKind
kindModifiers: string; // see ScriptElementKindModifier, comma separated
sortText: string;
/**
* An optional span that indicates the text to be replaced by this completion item. It will be
* set if the required span differs from the one generated by the default replacement behavior and should
* be used in that case
*/
replacementSpan?: TextSpan;
}
export interface CompletionEntryDetails {
name: string;
kind: string; // see ScriptElementKind
kindModifiers: string; // see ScriptElementKindModifier, comma separated
displayParts: SymbolDisplayPart[];
documentation: SymbolDisplayPart[];
}
export interface OutliningSpan {
/** The span of the document to actually collapse. */
textSpan: TextSpan;
/** The span of the document to display when the user hovers over the collapsed span. */
hintSpan: TextSpan;
/** The text to display in the editor for the collapsed region. */
bannerText: string;
/**
* Whether or not this region should be automatically collapsed when
* the 'Collapse to Definitions' command is invoked.
*/
autoCollapse: boolean;
}
export interface EmitOutput {
outputFiles: OutputFile[];
emitSkipped: boolean;
}
export const enum OutputFileType {
JavaScript,
SourceMap,
Declaration
}
export interface OutputFile {
name: string;
writeByteOrderMark: boolean;
text: string;
}
export const enum EndOfLineState {
None,
InMultiLineCommentTrivia,
InSingleQuoteStringLiteral,
InDoubleQuoteStringLiteral,
InTemplateHeadOrNoSubstitutionTemplate,
InTemplateMiddleOrTail,
InTemplateSubstitutionPosition,
}
export enum TokenClass {
Punctuation,
Keyword,
Operator,
Comment,
Whitespace,
Identifier,
NumberLiteral,
StringLiteral,
RegExpLiteral,
}
export interface ClassificationResult {
finalLexState: EndOfLineState;
entries: ClassificationInfo[];
}
export interface ClassificationInfo {
length: number;
classification: TokenClass;
}
export interface Classifier {
/**
* Gives lexical classifications of tokens on a line without any syntactic context.
* For instance, a token consisting of the text 'string' can be either an identifier
* named 'string' or the keyword 'string', however, because this classifier is not aware,
* it relies on certain heuristics to give acceptable results. For classifications where
* speed trumps accuracy, this function is preferable; however, for true accuracy, the
* syntactic classifier is ideal. In fact, in certain editing scenarios, combining the
* lexical, syntactic, and semantic classifiers may issue the best user experience.
*
* @param text The text of a line to classify.
* @param lexState The state of the lexical classifier at the end of the previous line.
* @param syntacticClassifierAbsent Whether the client is *not* using a syntactic classifier.
* If there is no syntactic classifier (syntacticClassifierAbsent=true),
* certain heuristics may be used in its place; however, if there is a
* syntactic classifier (syntacticClassifierAbsent=false), certain
* classifications which may be incorrectly categorized will be given
* back as Identifiers in order to allow the syntactic classifier to
* subsume the classification.
* @deprecated Use getLexicalClassifications instead.
*/
getClassificationsForLine(text: string, lexState: EndOfLineState, syntacticClassifierAbsent: boolean): ClassificationResult;
getEncodedLexicalClassifications(text: string, endOfLineState: EndOfLineState, syntacticClassifierAbsent: boolean): Classifications;
}
// TODO: move these to enums
export namespace ScriptElementKind {
export const unknown = "";
export const warning = "warning";
/** predefined type (void) or keyword (class) */
export const keyword = "keyword";
/** top level script node */
export const scriptElement = "script";
/** module foo {} */
export const moduleElement = "module";
/** class X {} */
export const classElement = "class";
/** var x = class X {} */
export const localClassElement = "local class";
/** interface Y {} */
export const interfaceElement = "interface";
/** type T = ... */
export const typeElement = "type";
/** enum E */
export const enumElement = "enum";
// TODO: GH#9983
export const enumMemberElement = "const";
/**
* Inside module and script only
* const v = ..
*/
export const variableElement = "var";
/** Inside function */
export const localVariableElement = "local var";
/**
* Inside module and script only
* function f() { }
*/
export const functionElement = "function";
/** Inside function */
export const localFunctionElement = "local function";
/** class X { [public|private]* foo() {} } */
export const memberFunctionElement = "method";
/** class X { [public|private]* [get|set] foo:number; } */
export const memberGetAccessorElement = "getter";
export const memberSetAccessorElement = "setter";
/**
* class X { [public|private]* foo:number; }
* interface Y { foo:number; }
*/
export const memberVariableElement = "property";
/** class X { constructor() { } } */
export const constructorImplementationElement = "constructor";
/** interface Y { ():number; } */
export const callSignatureElement = "call";
/** interface Y { []:number; } */
export const indexSignatureElement = "index";
/** interface Y { new():Y; } */
export const constructSignatureElement = "construct";
/** function foo(*Y*: string) */
export const parameterElement = "parameter";
export const typeParameterElement = "type parameter";
export const primitiveType = "primitive type";
export const label = "label";
export const alias = "alias";
export const constElement = "const";
export const letElement = "let";
export const directory = "directory";
export const externalModuleName = "external module name";
}
export namespace ScriptElementKindModifier {
export const none = "";
export const publicMemberModifier = "public";
export const privateMemberModifier = "private";
export const protectedMemberModifier = "protected";
export const exportedModifier = "export";
export const ambientModifier = "declare";
export const staticModifier = "static";
export const abstractModifier = "abstract";
}
export class ClassificationTypeNames {
public static comment = "comment";
public static identifier = "identifier";
public static keyword = "keyword";
public static numericLiteral = "number";
public static operator = "operator";
public static stringLiteral = "string";
public static whiteSpace = "whitespace";
public static text = "text";
public static punctuation = "punctuation";
public static className = "class name";
public static enumName = "enum name";
public static interfaceName = "interface name";
public static moduleName = "module name";
public static typeParameterName = "type parameter name";
public static typeAliasName = "type alias name";
public static parameterName = "parameter name";
public static docCommentTagName = "doc comment tag name";
public static jsxOpenTagName = "jsx open tag name";
public static jsxCloseTagName = "jsx close tag name";
public static jsxSelfClosingTagName = "jsx self closing tag name";
public static jsxAttribute = "jsx attribute";
public static jsxText = "jsx text";
public static jsxAttributeStringLiteralValue = "jsx attribute string literal value";
}
export const enum ClassificationType {
comment = 1,
identifier = 2,
keyword = 3,
numericLiteral = 4,
operator = 5,
stringLiteral = 6,
regularExpressionLiteral = 7,
whiteSpace = 8,
text = 9,
punctuation = 10,
className = 11,
enumName = 12,
interfaceName = 13,
moduleName = 14,
typeParameterName = 15,
typeAliasName = 16,
parameterName = 17,
docCommentTagName = 18,
jsxOpenTagName = 19,
jsxCloseTagName = 20,
jsxSelfClosingTagName = 21,
jsxAttribute = 22,
jsxText = 23,
jsxAttributeStringLiteralValue = 24,
}
}
+394 -11
View File
@@ -1,6 +1,387 @@
// These utilities are common to multiple language service features.
/* @internal */
namespace ts {
export const scanner: Scanner = createScanner(ScriptTarget.Latest, /*skipTrivia*/ true);
export const emptyArray: any[] = [];
export const enum SemanticMeaning {
None = 0x0,
Value = 0x1,
Type = 0x2,
Namespace = 0x4,
All = Value | Type | Namespace
}
export function getMeaningFromDeclaration(node: Node): SemanticMeaning {
switch (node.kind) {
case SyntaxKind.Parameter:
case SyntaxKind.VariableDeclaration:
case SyntaxKind.BindingElement:
case SyntaxKind.PropertyDeclaration:
case SyntaxKind.PropertySignature:
case SyntaxKind.PropertyAssignment:
case SyntaxKind.ShorthandPropertyAssignment:
case SyntaxKind.EnumMember:
case SyntaxKind.MethodDeclaration:
case SyntaxKind.MethodSignature:
case SyntaxKind.Constructor:
case SyntaxKind.GetAccessor:
case SyntaxKind.SetAccessor:
case SyntaxKind.FunctionDeclaration:
case SyntaxKind.FunctionExpression:
case SyntaxKind.ArrowFunction:
case SyntaxKind.CatchClause:
return SemanticMeaning.Value;
case SyntaxKind.TypeParameter:
case SyntaxKind.InterfaceDeclaration:
case SyntaxKind.TypeAliasDeclaration:
case SyntaxKind.TypeLiteral:
return SemanticMeaning.Type;
case SyntaxKind.ClassDeclaration:
case SyntaxKind.EnumDeclaration:
return SemanticMeaning.Value | SemanticMeaning.Type;
case SyntaxKind.ModuleDeclaration:
if (isAmbientModule(<ModuleDeclaration>node)) {
return SemanticMeaning.Namespace | SemanticMeaning.Value;
}
else if (getModuleInstanceState(node) === ModuleInstanceState.Instantiated) {
return SemanticMeaning.Namespace | SemanticMeaning.Value;
}
else {
return SemanticMeaning.Namespace;
}
case SyntaxKind.NamedImports:
case SyntaxKind.ImportSpecifier:
case SyntaxKind.ImportEqualsDeclaration:
case SyntaxKind.ImportDeclaration:
case SyntaxKind.ExportAssignment:
case SyntaxKind.ExportDeclaration:
return SemanticMeaning.Value | SemanticMeaning.Type | SemanticMeaning.Namespace;
// An external module can be a Value
case SyntaxKind.SourceFile:
return SemanticMeaning.Namespace | SemanticMeaning.Value;
}
return SemanticMeaning.Value | SemanticMeaning.Type | SemanticMeaning.Namespace;
}
export function getMeaningFromLocation(node: Node): SemanticMeaning {
if (node.parent.kind === SyntaxKind.ExportAssignment) {
return SemanticMeaning.Value | SemanticMeaning.Type | SemanticMeaning.Namespace;
}
else if (isInRightSideOfImport(node)) {
return getMeaningFromRightHandSideOfImportEquals(node);
}
else if (isDeclarationName(node)) {
return getMeaningFromDeclaration(node.parent);
}
else if (isTypeReference(node)) {
return SemanticMeaning.Type;
}
else if (isNamespaceReference(node)) {
return SemanticMeaning.Namespace;
}
else {
return SemanticMeaning.Value;
}
}
function getMeaningFromRightHandSideOfImportEquals(node: Node) {
Debug.assert(node.kind === SyntaxKind.Identifier);
// import a = |b|; // Namespace
// import a = |b.c|; // Value, type, namespace
// import a = |b.c|.d; // Namespace
if (node.parent.kind === SyntaxKind.QualifiedName &&
(<QualifiedName>node.parent).right === node &&
node.parent.parent.kind === SyntaxKind.ImportEqualsDeclaration) {
return SemanticMeaning.Value | SemanticMeaning.Type | SemanticMeaning.Namespace;
}
return SemanticMeaning.Namespace;
}
function isInRightSideOfImport(node: Node) {
while (node.parent.kind === SyntaxKind.QualifiedName) {
node = node.parent;
}
return isInternalModuleImportEqualsDeclaration(node.parent) && (<ImportEqualsDeclaration>node.parent).moduleReference === node;
}
function isNamespaceReference(node: Node): boolean {
return isQualifiedNameNamespaceReference(node) || isPropertyAccessNamespaceReference(node);
}
function isQualifiedNameNamespaceReference(node: Node): boolean {
let root = node;
let isLastClause = true;
if (root.parent.kind === SyntaxKind.QualifiedName) {
while (root.parent && root.parent.kind === SyntaxKind.QualifiedName) {
root = root.parent;
}
isLastClause = (<QualifiedName>root).right === node;
}
return root.parent.kind === SyntaxKind.TypeReference && !isLastClause;
}
function isPropertyAccessNamespaceReference(node: Node): boolean {
let root = node;
let isLastClause = true;
if (root.parent.kind === SyntaxKind.PropertyAccessExpression) {
while (root.parent && root.parent.kind === SyntaxKind.PropertyAccessExpression) {
root = root.parent;
}
isLastClause = (<PropertyAccessExpression>root).name === node;
}
if (!isLastClause && root.parent.kind === SyntaxKind.ExpressionWithTypeArguments && root.parent.parent.kind === SyntaxKind.HeritageClause) {
const decl = root.parent.parent.parent;
return (decl.kind === SyntaxKind.ClassDeclaration && (<HeritageClause>root.parent.parent).token === SyntaxKind.ImplementsKeyword) ||
(decl.kind === SyntaxKind.InterfaceDeclaration && (<HeritageClause>root.parent.parent).token === SyntaxKind.ExtendsKeyword);
}
return false;
}
function isTypeReference(node: Node): boolean {
if (isRightSideOfQualifiedNameOrPropertyAccess(node)) {
node = node.parent;
}
return node.parent.kind === SyntaxKind.TypeReference ||
(node.parent.kind === SyntaxKind.ExpressionWithTypeArguments && !isExpressionWithTypeArgumentsInClassExtendsClause(<ExpressionWithTypeArguments>node.parent)) ||
(node.kind === SyntaxKind.ThisKeyword && !isPartOfExpression(node)) ||
node.kind === SyntaxKind.ThisType;
}
export function isCallExpressionTarget(node: Node): boolean {
return isCallOrNewExpressionTarget(node, SyntaxKind.CallExpression);
}
export function isNewExpressionTarget(node: Node): boolean {
return isCallOrNewExpressionTarget(node, SyntaxKind.NewExpression);
}
function isCallOrNewExpressionTarget(node: Node, kind: SyntaxKind) {
const target = climbPastPropertyAccess(node);
return target && target.parent && target.parent.kind === kind && (<CallExpression>target.parent).expression === target;
}
export function climbPastPropertyAccess(node: Node) {
return isRightSideOfPropertyAccess(node) ? node.parent : node;
}
export function getTargetLabel(referenceNode: Node, labelName: string): Identifier {
while (referenceNode) {
if (referenceNode.kind === SyntaxKind.LabeledStatement && (<LabeledStatement>referenceNode).label.text === labelName) {
return (<LabeledStatement>referenceNode).label;
}
referenceNode = referenceNode.parent;
}
return undefined;
}
export function isJumpStatementTarget(node: Node): boolean {
return node.kind === SyntaxKind.Identifier &&
(node.parent.kind === SyntaxKind.BreakStatement || node.parent.kind === SyntaxKind.ContinueStatement) &&
(<BreakOrContinueStatement>node.parent).label === node;
}
function isLabelOfLabeledStatement(node: Node): boolean {
return node.kind === SyntaxKind.Identifier &&
node.parent.kind === SyntaxKind.LabeledStatement &&
(<LabeledStatement>node.parent).label === node;
}
export function isLabelName(node: Node): boolean {
return isLabelOfLabeledStatement(node) || isJumpStatementTarget(node);
}
export function isRightSideOfQualifiedName(node: Node) {
return node.parent.kind === SyntaxKind.QualifiedName && (<QualifiedName>node.parent).right === node;
}
export function isRightSideOfPropertyAccess(node: Node) {
return node && node.parent && node.parent.kind === SyntaxKind.PropertyAccessExpression && (<PropertyAccessExpression>node.parent).name === node;
}
export function isNameOfModuleDeclaration(node: Node) {
return node.parent.kind === SyntaxKind.ModuleDeclaration && (<ModuleDeclaration>node.parent).name === node;
}
export function isNameOfFunctionDeclaration(node: Node): boolean {
return node.kind === SyntaxKind.Identifier &&
isFunctionLike(node.parent) && (<FunctionLikeDeclaration>node.parent).name === node;
}
export function isLiteralNameOfPropertyDeclarationOrIndexAccess(node: Node): boolean {
if (node.kind === SyntaxKind.StringLiteral || node.kind === SyntaxKind.NumericLiteral) {
switch (node.parent.kind) {
case SyntaxKind.PropertyDeclaration:
case SyntaxKind.PropertySignature:
case SyntaxKind.PropertyAssignment:
case SyntaxKind.EnumMember:
case SyntaxKind.MethodDeclaration:
case SyntaxKind.MethodSignature:
case SyntaxKind.GetAccessor:
case SyntaxKind.SetAccessor:
case SyntaxKind.ModuleDeclaration:
return (<Declaration>node.parent).name === node;
case SyntaxKind.ElementAccessExpression:
return (<ElementAccessExpression>node.parent).argumentExpression === node;
case SyntaxKind.ComputedPropertyName:
return true;
}
}
return false;
}
export function isExpressionOfExternalModuleImportEqualsDeclaration(node: Node) {
return isExternalModuleImportEqualsDeclaration(node.parent.parent) &&
getExternalModuleImportEqualsDeclarationExpression(node.parent.parent) === node;
}
/** Returns true if the position is within a comment */
export function isInsideComment(sourceFile: SourceFile, token: Node, position: number): boolean {
// The position has to be: 1. in the leading trivia (before token.getStart()), and 2. within a comment
return position <= token.getStart(sourceFile) &&
(isInsideCommentRange(getTrailingCommentRanges(sourceFile.text, token.getFullStart())) ||
isInsideCommentRange(getLeadingCommentRanges(sourceFile.text, token.getFullStart())));
function isInsideCommentRange(comments: CommentRange[]): boolean {
return forEach(comments, comment => {
// either we are 1. completely inside the comment, or 2. at the end of the comment
if (comment.pos < position && position < comment.end) {
return true;
}
else if (position === comment.end) {
const text = sourceFile.text;
const width = comment.end - comment.pos;
// is single line comment or just /*
if (width <= 2 || text.charCodeAt(comment.pos + 1) === CharacterCodes.slash) {
return true;
}
else {
// is unterminated multi-line comment
return !(text.charCodeAt(comment.end - 1) === CharacterCodes.slash &&
text.charCodeAt(comment.end - 2) === CharacterCodes.asterisk);
}
}
return false;
});
}
}
export function getContainerNode(node: Node): Declaration {
while (true) {
node = node.parent;
if (!node) {
return undefined;
}
switch (node.kind) {
case SyntaxKind.SourceFile:
case SyntaxKind.MethodDeclaration:
case SyntaxKind.MethodSignature:
case SyntaxKind.FunctionDeclaration:
case SyntaxKind.FunctionExpression:
case SyntaxKind.GetAccessor:
case SyntaxKind.SetAccessor:
case SyntaxKind.ClassDeclaration:
case SyntaxKind.InterfaceDeclaration:
case SyntaxKind.EnumDeclaration:
case SyntaxKind.ModuleDeclaration:
return <Declaration>node;
}
}
}
export function getNodeKind(node: Node): string {
switch (node.kind) {
case SyntaxKind.SourceFile:
return isExternalModule(<SourceFile>node) ? ScriptElementKind.moduleElement : ScriptElementKind.scriptElement;
case SyntaxKind.ModuleDeclaration:
return ScriptElementKind.moduleElement;
case SyntaxKind.ClassDeclaration:
case SyntaxKind.ClassExpression:
return ScriptElementKind.classElement;
case SyntaxKind.InterfaceDeclaration: return ScriptElementKind.interfaceElement;
case SyntaxKind.TypeAliasDeclaration: return ScriptElementKind.typeElement;
case SyntaxKind.EnumDeclaration: return ScriptElementKind.enumElement;
case SyntaxKind.VariableDeclaration:
return getKindOfVariableDeclaration(<VariableDeclaration>node);
case SyntaxKind.BindingElement:
return getKindOfVariableDeclaration(<VariableDeclaration>getRootDeclaration(node));
case SyntaxKind.ArrowFunction:
case SyntaxKind.FunctionDeclaration:
case SyntaxKind.FunctionExpression:
return ScriptElementKind.functionElement;
case SyntaxKind.GetAccessor: return ScriptElementKind.memberGetAccessorElement;
case SyntaxKind.SetAccessor: return ScriptElementKind.memberSetAccessorElement;
case SyntaxKind.MethodDeclaration:
case SyntaxKind.MethodSignature:
return ScriptElementKind.memberFunctionElement;
case SyntaxKind.PropertyDeclaration:
case SyntaxKind.PropertySignature:
return ScriptElementKind.memberVariableElement;
case SyntaxKind.IndexSignature: return ScriptElementKind.indexSignatureElement;
case SyntaxKind.ConstructSignature: return ScriptElementKind.constructSignatureElement;
case SyntaxKind.CallSignature: return ScriptElementKind.callSignatureElement;
case SyntaxKind.Constructor: return ScriptElementKind.constructorImplementationElement;
case SyntaxKind.TypeParameter: return ScriptElementKind.typeParameterElement;
case SyntaxKind.EnumMember: return ScriptElementKind.enumMemberElement;
case SyntaxKind.Parameter: return hasModifier(node, ModifierFlags.ParameterPropertyModifier) ? ScriptElementKind.memberVariableElement : ScriptElementKind.parameterElement;
case SyntaxKind.ImportEqualsDeclaration:
case SyntaxKind.ImportSpecifier:
case SyntaxKind.ImportClause:
case SyntaxKind.ExportSpecifier:
case SyntaxKind.NamespaceImport:
return ScriptElementKind.alias;
case SyntaxKind.JSDocTypedefTag:
return ScriptElementKind.typeElement;
default:
return ScriptElementKind.unknown;
}
function getKindOfVariableDeclaration(v: VariableDeclaration): string {
return isConst(v)
? ScriptElementKind.constElement
: isLet(v)
? ScriptElementKind.letElement
: ScriptElementKind.variableElement;
}
}
export function getStringLiteralTypeForNode(node: StringLiteral | LiteralTypeNode, typeChecker: TypeChecker): LiteralType {
const searchNode = node.parent.kind === SyntaxKind.LiteralType ? <LiteralTypeNode>node.parent : node;
const type = typeChecker.getTypeAtLocation(searchNode);
if (type && type.flags & TypeFlags.StringLiteral) {
return <LiteralType>type;
}
return undefined;
}
export function isThis(node: Node): boolean {
switch (node.kind) {
case SyntaxKind.ThisKeyword:
// case SyntaxKind.ThisType: TODO: GH#9267
return true;
case SyntaxKind.Identifier:
// 'this' as a parameter
return (node as Identifier).originalKeywordKind === SyntaxKind.ThisKeyword && node.parent.kind === SyntaxKind.Parameter;
default:
return false;
}
}
// Matches the beginning of a triple slash directive
const tripleSlashDirectivePrefixRegex = /^\/\/\/\s*</;
@@ -572,9 +953,11 @@ namespace ts {
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;
if (jsDocComment.tags) {
for (const tag of jsDocComment.tags) {
if (tag.pos <= position && position <= tag.end) {
return tag;
}
}
}
}
@@ -591,15 +974,15 @@ namespace ts {
}
export function getNodeModifiers(node: Node): string {
const flags = getCombinedNodeFlags(node);
const flags = getCombinedModifierFlags(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 (flags & ModifierFlags.Private) result.push(ScriptElementKindModifier.privateMemberModifier);
if (flags & ModifierFlags.Protected) result.push(ScriptElementKindModifier.protectedMemberModifier);
if (flags & ModifierFlags.Public) result.push(ScriptElementKindModifier.publicMemberModifier);
if (flags & ModifierFlags.Static) result.push(ScriptElementKindModifier.staticModifier);
if (flags & ModifierFlags.Abstract) result.push(ScriptElementKindModifier.abstractModifier);
if (flags & ModifierFlags.Export) result.push(ScriptElementKindModifier.exportedModifier);
if (isInAmbientContext(node)) result.push(ScriptElementKindModifier.ambientModifier);
return result.length > 0 ? result.join(",") : ScriptElementKindModifier.none;
@@ -975,4 +1358,4 @@ namespace ts {
diagnostics: error ? concatenate(diagnostics, [error]) : diagnostics
};
}
}
}