Merge pull request #708 from Microsoft/classifiedQuickInfo

Initial work on classified quick info.
This commit is contained in:
CyrusNajmabadi 2014-09-23 15:48:15 -07:00
commit 8be8e1f5be
7 changed files with 791 additions and 207 deletions

View File

@ -23,6 +23,28 @@ module ts {
return undefined;
}
interface SymbolWriter {
writeKind(text: string, kind: SymbolDisplayPartKind): void;
writeSymbol(text: string, symbol: Symbol): void;
writeLine(): void;
increaseIndent(): void;
decreaseIndent(): void;
clear(): void;
// Called when the symbol writer encounters a symbol to write. Currently only used by the
// declaration emitter to help determine if it should patch up the final declaration file
// with import statements it previously saw (but chose not to emit).
trackSymbol(symbol: Symbol, enclosingDeclaration?: Node, meaning?: SymbolFlags): void;
}
interface DisplayPartsSymbolWriter extends SymbolWriter {
displayParts(): SymbolDisplayPart[];
}
interface StringSymbolWriter extends SymbolWriter {
string(): string;
}
/// fullTypeCheck denotes if this instance of the typechecker will be used to get semantic diagnostics.
/// If fullTypeCheck === true, then the typechecker should do every possible check to produce all errors
/// If fullTypeCheck === false, the typechecker can take shortcuts and skip checks that only produce errors.
@ -62,11 +84,14 @@ module ts {
getTypeOfNode: getTypeOfNode,
getApparentType: getApparentType,
typeToString: typeToString,
typeToDisplayParts: typeToDisplayParts,
symbolToString: symbolToString,
symbolToDisplayParts: symbolToDisplayParts,
getAugmentedPropertiesOfApparentType: getAugmentedPropertiesOfApparentType,
getRootSymbol: getRootSymbol,
getContextualType: getContextualType,
getFullyQualifiedName: getFullyQualifiedName
getFullyQualifiedName: getFullyQualifiedName,
getEnumMemberValue: getEnumMemberValue
};
var undefinedSymbol = createSymbol(SymbolFlags.Property | SymbolFlags.Transient, "undefined");
@ -895,106 +920,238 @@ module ts {
{ accessibility: SymbolAccessibility.NotAccessible, errorSymbolName: firstIdentifierName };
}
// Pool writers to avoid needing to allocate them for every symbol we write.
var displayPartWriters: DisplayPartsSymbolWriter[] = [];
var stringWriters: StringSymbolWriter[] = [];
function displayPartKind(symbol: Symbol): SymbolDisplayPartKind {
var flags = symbol.flags;
if (flags & SymbolFlags.Variable) {
return symbol.declarations && symbol.declarations.length > 0 && symbol.declarations[0].kind === SyntaxKind.Parameter
? SymbolDisplayPartKind.parameterName
: SymbolDisplayPartKind.localName;
}
else if (flags & SymbolFlags.Property) { return SymbolDisplayPartKind.propertyName; }
else if (flags & SymbolFlags.EnumMember) { return SymbolDisplayPartKind.enumMemberName; }
else if (flags & SymbolFlags.Function) { return SymbolDisplayPartKind.functionName; }
else if (flags & SymbolFlags.Class) { return SymbolDisplayPartKind.className; }
else if (flags & SymbolFlags.Interface) { return SymbolDisplayPartKind.interfaceName; }
else if (flags & SymbolFlags.Enum) { return SymbolDisplayPartKind.enumName; }
else if (flags & SymbolFlags.Module) { return SymbolDisplayPartKind.moduleName; }
else if (flags & SymbolFlags.Method) { return SymbolDisplayPartKind.methodName; }
else if (flags & SymbolFlags.TypeParameter) { return SymbolDisplayPartKind.typeParameterName; }
return SymbolDisplayPartKind.text;
}
function getDisplayPartWriter(): DisplayPartsSymbolWriter {
if (displayPartWriters.length == 0) {
var displayParts: SymbolDisplayPart[] = [];
return {
displayParts: () => displayParts,
writeKind: (text, kind) => displayParts.push(new SymbolDisplayPart(text, kind, undefined)),
writeSymbol: (text, symbol) => displayParts.push(new SymbolDisplayPart(text, displayPartKind(symbol), symbol)),
// Completely ignore indentation for display part writers. And map newlines to
// a single space.
writeLine: () => displayParts.push(new SymbolDisplayPart(" ", SymbolDisplayPartKind.space, undefined)),
increaseIndent: () => { },
decreaseIndent: () => { },
clear: () => displayParts = [],
trackSymbol: () => { }
};
}
return displayPartWriters.pop();
}
function getStringWriter(): StringSymbolWriter {
if (stringWriters.length == 0) {
var str = "";
return {
string: () => str,
writeKind: text => str += text,
writeSymbol: text => str += text,
// Completely ignore indentation for string writers. And map newlines to
// a single space.
writeLine: () => str += " ",
increaseIndent: () => { },
decreaseIndent: () => { },
clear: () => str = "",
trackSymbol: () => { }
};
}
return stringWriters.pop();
}
function releaseDisplayPartWriter(writer: DisplayPartsSymbolWriter) {
writer.clear();
displayPartWriters.push(writer);
}
function releaseStringWriter(writer: StringSymbolWriter) {
writer.clear()
stringWriters.push(writer);
}
function writeKeyword(writer: SymbolWriter, kind: SyntaxKind) {
writer.writeKind(tokenToString(kind), SymbolDisplayPartKind.keyword);
}
function writePunctuation(writer: SymbolWriter, kind: SyntaxKind) {
writer.writeKind(tokenToString(kind), SymbolDisplayPartKind.punctuation);
}
function writeOperator(writer: SymbolWriter, kind: SyntaxKind) {
writer.writeKind(tokenToString(kind), SymbolDisplayPartKind.operator);
}
function writeSpace(writer: SymbolWriter) {
writer.writeKind(" ", SymbolDisplayPartKind.space);
}
function symbolToString(symbol: Symbol, enclosingDeclaration?: Node, meaning?: SymbolFlags): string {
var writer = getStringWriter();
writeSymbol(symbol, writer, enclosingDeclaration, meaning);
var result = writer.string();
releaseStringWriter(writer);
return result;
}
function symbolToDisplayParts(symbol: Symbol, enclosingDeclaration?: Node, meaning?: SymbolFlags): SymbolDisplayPart[] {
var writer = getDisplayPartWriter();
writeSymbol(symbol, writer, enclosingDeclaration, meaning);
var result = writer.displayParts();
releaseDisplayPartWriter(writer);
return result;
}
// Enclosing declaration is optional when we don't want to get qualified name in the enclosing declaration scope
// Meaning needs to be specified if the enclosing declaration is given
function symbolToString(symbol: Symbol, enclosingDeclaration?: Node, meaning?: SymbolFlags) {
function getSymbolName(symbol: Symbol) {
function writeSymbol(symbol: Symbol, writer: SymbolWriter, enclosingDeclaration?: Node, meaning?: SymbolFlags): void {
function writeSymbolName(symbol: Symbol): void {
if (symbol.declarations && symbol.declarations.length > 0) {
var declaration = symbol.declarations[0];
if (declaration.name) {
return identifierToString(declaration.name);
writer.writeSymbol(identifierToString(declaration.name), symbol);
return;
}
}
writer.writeSymbol(symbol.name, symbol);
}
// Let the writer know we just wrote out a symbol. The declarationemitter writer uses
// this to determine if an import it has previously seen (and not writter out) needs
// to be written to the file once the walk of the tree is complete.
//
// NOTE(cyrusn): This approach feels somewhat unfortunate. A simple pass over the tree
// up front (for example, during checking) could determien if we need to emit the imports
// and we could then access that data during declaration emit.
writer.trackSymbol(symbol, enclosingDeclaration, meaning);
var needsDot = false;
function walkSymbol(symbol: Symbol, meaning: SymbolFlags): void {
if (symbol) {
var accessibleSymbolChain = getAccessibleSymbolChain(symbol, enclosingDeclaration, meaning);
if (!accessibleSymbolChain ||
needsQualification(accessibleSymbolChain[0], enclosingDeclaration, accessibleSymbolChain.length === 1 ? meaning : getQualifiedLeftMeaning(meaning))) {
// Go up and add our parent.
walkSymbol(
getParentOfSymbol(accessibleSymbolChain ? accessibleSymbolChain[0] : symbol),
getQualifiedLeftMeaning(meaning));
}
if (accessibleSymbolChain) {
for (var i = 0, n = accessibleSymbolChain.length; i < n; i++) {
if (needsDot) {
writePunctuation(writer, SyntaxKind.DotToken);
}
writeSymbolName(accessibleSymbolChain[i]);
needsDot = true;
}
}
else {
// If we didn't find accessible symbol chain for this symbol, break if this is external module
if (!needsDot && ts.forEach(symbol.declarations, declaration => hasExternalModuleSymbol(declaration))) {
return;
}
if (needsDot) {
writePunctuation(writer, SyntaxKind.DotToken);
}
writeSymbolName(symbol);
needsDot = true;
}
}
return symbol.name;
}
// Get qualified name
if (enclosingDeclaration &&
// TypeParameters do not need qualification
!(symbol.flags & SymbolFlags.TypeParameter)) {
var symbolName: string;
while (symbol) {
var isFirstName = !symbolName;
var accessibleSymbolChain = getAccessibleSymbolChain(symbol, enclosingDeclaration, meaning);
var currentSymbolName: string;
if (accessibleSymbolChain) {
currentSymbolName = ts.map(accessibleSymbolChain, accessibleSymbol => getSymbolName(accessibleSymbol)).join(".");
}
else {
// If we didn't find accessible symbol chain for this symbol, break if this is external module
if (!isFirstName && ts.forEach(symbol.declarations, declaration => hasExternalModuleSymbol(declaration))) {
break;
}
currentSymbolName = getSymbolName(symbol);
}
symbolName = currentSymbolName + (isFirstName ? "" : ("." + symbolName));
if (accessibleSymbolChain && !needsQualification(accessibleSymbolChain[0], enclosingDeclaration, accessibleSymbolChain.length === 1 ? meaning : getQualifiedLeftMeaning(meaning))) {
break;
}
symbol = getParentOfSymbol(accessibleSymbolChain ? accessibleSymbolChain[0] : symbol);
meaning = getQualifiedLeftMeaning(meaning);
}
return symbolName;
walkSymbol(symbol, meaning);
return;
}
return getSymbolName(symbol);
return writeSymbolName(symbol);
}
function writeSymbolToTextWriter(symbol: Symbol, enclosingDeclaration: Node, meaning: SymbolFlags, writer: TextWriter) {
writer.write(symbolToString(symbol, enclosingDeclaration, meaning));
}
function createSingleLineTextWriter(maxLength?: number) {
var result = "";
var overflow = false;
function write(s: string) {
if (!overflow) {
result += s;
if (result.length > maxLength) {
result = result.substr(0, maxLength - 3) + "...";
overflow = true;
}
}
}
return {
write: write,
writeSymbol(symbol: Symbol, enclosingDeclaration?: Node, meaning?: SymbolFlags) {
writeSymbolToTextWriter(symbol, enclosingDeclaration, meaning, this);
},
writeLine() {
write(" ");
},
increaseIndent() { },
decreaseIndent() { },
getText() {
return result;
}
};
}
function typeToString(type: Type, enclosingDeclaration?: Node, flags?: TypeFormatFlags): string {
var writer = getStringWriter();
writeType(type, writer, enclosingDeclaration, flags);
var result = writer.string();
releaseStringWriter(writer);
var maxLength = compilerOptions.noErrorTruncation || flags & TypeFormatFlags.NoTruncation ? undefined : 100;
var stringWriter = createSingleLineTextWriter(maxLength);
// TODO(shkamat): typeToString should take enclosingDeclaration as input, once we have implemented enclosingDeclaration
writeTypeToTextWriter(type, enclosingDeclaration, flags, stringWriter);
return stringWriter.getText();
if (maxLength && result.length >= maxLength) {
result = result.substr(0, maxLength - "...".length) + "...";
}
return result;
}
function writeTypeToTextWriter(type: Type, enclosingDeclaration: Node, flags: TypeFormatFlags, writer: TextWriter) {
function typeToDisplayParts(type: Type, enclosingDeclaration?: Node, flags?: TypeFormatFlags): SymbolDisplayPart[] {
var writer = getDisplayPartWriter();
writeType(type, writer, enclosingDeclaration, flags);
var result = writer.displayParts();
releaseDisplayPartWriter(writer);
return result;
}
function writeType(type: Type, writer: SymbolWriter, enclosingDeclaration?: Node, flags?: TypeFormatFlags) {
var typeStack: Type[];
return writeType(type, /*allowFunctionOrConstructorTypeLiteral*/ true);
function writeType(type: Type, allowFunctionOrConstructorTypeLiteral: boolean) {
if (type.flags & TypeFlags.Intrinsic) {
writer.write((<IntrinsicType>type).intrinsicName);
writer.writeKind((<IntrinsicType>type).intrinsicName, SymbolDisplayPartKind.keyword);
}
else if (type.flags & TypeFlags.Reference) {
writeTypeReference(<TypeReference>type);
}
else if (type.flags & (TypeFlags.Class | TypeFlags.Interface | TypeFlags.Enum | TypeFlags.TypeParameter)) {
writer.writeSymbol(type.symbol, enclosingDeclaration, SymbolFlags.Type);
writeSymbol(type.symbol, writer, enclosingDeclaration, SymbolFlags.Type);
}
else if (type.flags & TypeFlags.Tuple) {
writeTupleType(<TupleType>type);
@ -1003,18 +1160,24 @@ module ts {
writeAnonymousType(<ObjectType>type, allowFunctionOrConstructorTypeLiteral);
}
else if (type.flags & TypeFlags.StringLiteral) {
writer.write((<StringLiteralType>type).text);
writer.writeKind((<StringLiteralType>type).text, SymbolDisplayPartKind.stringLiteral);
}
else {
// Should never get here
writer.write("{ ... }");
// { ... }
writePunctuation(writer, SyntaxKind.OpenBraceToken);
writeSpace(writer);
writePunctuation(writer, SyntaxKind.DotDotDotToken);
writeSpace(writer);
writePunctuation(writer, SyntaxKind.CloseBraceToken);
}
}
function writeTypeList(types: Type[]) {
for (var i = 0; i < types.length; i++) {
if (i > 0) {
writer.write(", ");
writePunctuation(writer, SyntaxKind.CommaToken);
writeSpace(writer);
}
writeType(types[i], /*allowFunctionOrConstructorTypeLiteral*/ true);
}
@ -1025,20 +1188,21 @@ module ts {
// If we are writing array element type the arrow style signatures are not allowed as
// we need to surround it by curlies, e.g. { (): T; }[]; as () => T[] would mean something different
writeType(type.typeArguments[0], /*allowFunctionOrConstructorTypeLiteral*/ false);
writer.write("[]");
writePunctuation(writer, SyntaxKind.OpenBracketToken);
writePunctuation(writer, SyntaxKind.CloseBracketToken);
}
else {
writer.writeSymbol(type.target.symbol, enclosingDeclaration, SymbolFlags.Type);
writer.write("<");
writeSymbol(type.target.symbol, writer, enclosingDeclaration, SymbolFlags.Type);
writePunctuation(writer, SyntaxKind.LessThanToken);
writeTypeList(type.typeArguments);
writer.write(">");
writePunctuation(writer, SyntaxKind.GreaterThanToken);
}
}
function writeTupleType(type: TupleType) {
writer.write("[");
writePunctuation(writer, SyntaxKind.OpenBracketToken);
writeTypeList(type.elementTypes);
writer.write("]");
writePunctuation(writer, SyntaxKind.CloseBracketToken);
}
function writeAnonymousType(type: ObjectType, allowFunctionOrConstructorTypeLiteral: boolean) {
@ -1052,7 +1216,7 @@ module ts {
}
else if (typeStack && contains(typeStack, type)) {
// Recursive usage, use any
writer.write("any");
writeKeyword(writer, SyntaxKind.AnyKeyword);
}
else {
if (!typeStack) {
@ -1082,15 +1246,17 @@ module ts {
}
function writeTypeofSymbol(type: ObjectType) {
writer.write("typeof ");
writer.writeSymbol(type.symbol, enclosingDeclaration, SymbolFlags.Value);
writeKeyword(writer, SyntaxKind.TypeOfKeyword);
writeSpace(writer);
writeSymbol(type.symbol, writer, enclosingDeclaration, SymbolFlags.Value);
}
function writeLiteralType(type: ObjectType, allowFunctionOrConstructorTypeLiteral: boolean) {
var resolved = resolveObjectTypeMembers(type);
if (!resolved.properties.length && !resolved.stringIndexType && !resolved.numberIndexType) {
if (!resolved.callSignatures.length && !resolved.constructSignatures.length) {
writer.write("{}");
writePunctuation(writer, SyntaxKind.OpenBraceToken);
writePunctuation(writer, SyntaxKind.CloseBraceToken);
return;
}
@ -1100,37 +1266,56 @@ module ts {
return;
}
if (resolved.constructSignatures.length === 1 && !resolved.callSignatures.length) {
writer.write("new ");
writeKeyword(writer, SyntaxKind.NewKeyword);
writeSpace(writer);
writeSignature(resolved.constructSignatures[0], /*arrowStyle*/ true);
return;
}
}
}
writer.write("{");
writePunctuation(writer, SyntaxKind.OpenBraceToken);
writer.writeLine();
writer.increaseIndent();
for (var i = 0; i < resolved.callSignatures.length; i++) {
writeSignature(resolved.callSignatures[i]);
writer.write(";");
writePunctuation(writer, SyntaxKind.SemicolonToken);
writer.writeLine();
}
for (var i = 0; i < resolved.constructSignatures.length; i++) {
writer.write("new ");
writeKeyword(writer, SyntaxKind.NewKeyword);
writeSpace(writer);
writeSignature(resolved.constructSignatures[i]);
writer.write(";");
writePunctuation(writer, SyntaxKind.SemicolonToken);
writer.writeLine();
}
if (resolved.stringIndexType) {
writer.write("[x: string]: ");
// [x: string]:
writePunctuation(writer, SyntaxKind.OpenBracketToken);
writer.writeKind("x", SymbolDisplayPartKind.parameterName);
writePunctuation(writer, SyntaxKind.ColonToken);
writeSpace(writer);
writeKeyword(writer, SyntaxKind.StringKeyword);
writePunctuation(writer, SyntaxKind.CloseBracketToken);
writePunctuation(writer, SyntaxKind.ColonToken);
writeSpace(writer);
writeType(resolved.stringIndexType, /*allowFunctionOrConstructorTypeLiteral*/ true);
writer.write(";");
writePunctuation(writer, SyntaxKind.SemicolonToken);
writer.writeLine();
}
if (resolved.numberIndexType) {
writer.write("[x: number]: ");
// [x: number]:
writePunctuation(writer, SyntaxKind.OpenBracketToken);
writer.writeKind("x", SymbolDisplayPartKind.parameterName);
writePunctuation(writer, SyntaxKind.ColonToken);
writeSpace(writer);
writeKeyword(writer, SyntaxKind.NumberKeyword);
writePunctuation(writer, SyntaxKind.CloseBracketToken);
writePunctuation(writer, SyntaxKind.ColonToken);
writeSpace(writer);
writeType(resolved.numberIndexType, /*allowFunctionOrConstructorTypeLiteral*/ true);
writer.write(";");
writePunctuation(writer, SyntaxKind.SemicolonToken);
writer.writeLine();
}
for (var i = 0; i < resolved.properties.length; i++) {
@ -1139,64 +1324,81 @@ module ts {
if (p.flags & (SymbolFlags.Function | SymbolFlags.Method) && !getPropertiesOfType(t).length) {
var signatures = getSignaturesOfType(t, SignatureKind.Call);
for (var j = 0; j < signatures.length; j++) {
writer.writeSymbol(p);
writeSymbol(p, writer);
if (isOptionalProperty(p)) {
writer.write("?");
writePunctuation(writer, SyntaxKind.QuestionToken);
}
writeSignature(signatures[j]);
writer.write(";");
writePunctuation(writer, SyntaxKind.SemicolonToken);
writer.writeLine();
}
}
else {
writer.writeSymbol(p);
writeSymbol(p, writer);
if (isOptionalProperty(p)) {
writer.write("?");
writePunctuation(writer, SyntaxKind.QuestionToken);
}
writer.write(": ");
writePunctuation(writer, SyntaxKind.ColonToken);
writeSpace(writer);
writeType(t, /*allowFunctionOrConstructorTypeLiteral*/ true);
writer.write(";");
writePunctuation(writer, SyntaxKind.SemicolonToken);
writer.writeLine();
}
}
writer.decreaseIndent();
writer.write("}");
writePunctuation(writer, SyntaxKind.CloseBraceToken);
}
function writeSignature(signature: Signature, arrowStyle?: boolean) {
if (signature.typeParameters) {
writer.write("<");
writePunctuation(writer, SyntaxKind.LessThanToken);
for (var i = 0; i < signature.typeParameters.length; i++) {
if (i > 0) {
writer.write(", ");
writePunctuation(writer, SyntaxKind.CommaToken);
writeSpace(writer);
}
var tp = signature.typeParameters[i];
writer.writeSymbol(tp.symbol);
writeSymbol(tp.symbol, writer);
var constraint = getConstraintOfTypeParameter(tp);
if (constraint) {
writer.write(" extends ");
writeSpace(writer);
writeKeyword(writer, SyntaxKind.ExtendsKeyword);
writeSpace(writer);
writeType(constraint, /*allowFunctionOrConstructorTypeLiteral*/ true);
}
}
writer.write(">");
writePunctuation(writer, SyntaxKind.GreaterThanToken);
}
writer.write("(");
writePunctuation(writer, SyntaxKind.OpenParenToken);
for (var i = 0; i < signature.parameters.length; i++) {
if (i > 0) {
writer.write(", ");
writePunctuation(writer, SyntaxKind.CommaToken);
writeSpace(writer);
}
var p = signature.parameters[i];
if (getDeclarationFlagsFromSymbol(p) & NodeFlags.Rest) {
writer.write("...");
writePunctuation(writer, SyntaxKind.DotDotDotToken);
}
writer.writeSymbol(p);
writeSymbol(p, writer);
if (p.valueDeclaration.flags & NodeFlags.QuestionMark || (<VariableDeclaration>p.valueDeclaration).initializer) {
writer.write("?");
writePunctuation(writer, SyntaxKind.QuestionToken);
}
writer.write(": ");
writePunctuation(writer, SyntaxKind.ColonToken);
writeSpace(writer);
writeType(getTypeOfSymbol(p), /*allowFunctionOrConstructorTypeLiteral*/ true);
}
writer.write(arrowStyle ? ") => " : "): ");
writePunctuation(writer, SyntaxKind.CloseParenToken);
if (arrowStyle) {
writeSpace(writer);
writePunctuation(writer, SyntaxKind.EqualsGreaterThanToken);
}
else {
writePunctuation(writer, SyntaxKind.ColonToken);
}
writeSpace(writer);
writeType(getReturnTypeOfSignature(signature), /*allowFunctionOrConstructorTypeLiteral*/ true);
}
}
@ -6307,7 +6509,7 @@ module ts {
}
}
function getConstantValue(node: Expression): number {
function getConstantValueForExpression(node: Expression): number {
var isNegative = false;
if (node.kind === SyntaxKind.PrefixOperator) {
var unaryExpression = <UnaryExpression>node;
@ -6324,38 +6526,51 @@ module ts {
return undefined;
}
function computeEnumMemberValues(node: EnumDeclaration) {
var nodeLinks = getNodeLinks(node);
if (!(nodeLinks.flags & NodeCheckFlags.EnumValuesComputed)) {
var enumSymbol = getSymbolOfNode(node);
var enumType = getDeclaredTypeOfSymbol(enumSymbol);
var autoValue = 0;
var ambient = isInAmbientContext(node);
forEach(node.members, member => {
var initializer = member.initializer;
if (initializer) {
autoValue = getConstantValueForExpression(initializer);
if (autoValue === undefined && !ambient) {
// Only here do we need to check that the initializer is assignable to the enum type.
// If it is a constant value (not undefined), it is syntactically constrained to be a number.
// Also, we do not need to check this for ambients because there is already
// a syntax error if it is not a constant.
checkTypeAssignableTo(checkExpression(initializer), enumType, initializer, /*chainedMessage*/ undefined, /*terminalMessage*/ undefined);
}
}
else if (ambient) {
autoValue = undefined;
}
if (autoValue !== undefined) {
getNodeLinks(member).enumMemberValue = autoValue++;
}
});
nodeLinks.flags |= NodeCheckFlags.EnumValuesComputed;
}
}
function checkEnumDeclaration(node: EnumDeclaration) {
if (!fullTypeCheck) {
return;
}
checkTypeNameIsReserved(node.name, Diagnostics.Enum_name_cannot_be_0);
checkCollisionWithCapturedThisVariable(node, node.name);
checkCollistionWithRequireExportsInGeneratedCode(node, node.name);
checkExportsOnMergedDeclarations(node);
var enumSymbol = getSymbolOfNode(node);
var enumType = getDeclaredTypeOfSymbol(enumSymbol);
var autoValue = 0;
var ambient = isInAmbientContext(node);
forEach(node.members, member => {
var initializer = member.initializer;
if (initializer) {
autoValue = getConstantValue(initializer);
if (autoValue === undefined && !ambient) {
// Only here do we need to check that the initializer is assignable to the enum type.
// If it is a constant value (not undefined), it is syntactically constrained to be a number.
// Also, we do not need to check this for ambients because there is already
// a syntax error if it is not a constant.
checkTypeAssignableTo(checkExpression(initializer), enumType, initializer, /*chainedMessage*/ undefined, /*terminalMessage*/ undefined);
}
}
else if (ambient) {
autoValue = undefined;
}
if (autoValue !== undefined) {
getNodeLinks(member).enumMemberValue = autoValue++;
}
});
computeEnumMemberValues(node);
// Spec 2014 - Section 9.3:
// It isn't possible for one enum declaration to continue the automatic numbering sequence of another,
@ -6363,6 +6578,7 @@ module ts {
// for the first member.
//
// Only perform this check once per symbol
var enumSymbol = getSymbolOfNode(node);
var firstDeclaration = getDeclarationOfKind(enumSymbol, node.kind);
if (node === firstDeclaration) {
var seenEnumMissingInitialInitializer = false;
@ -7262,17 +7478,6 @@ module ts {
}
}
function getPropertyAccessSubstitution(node: PropertyAccess): string {
var symbol = getNodeLinks(node).resolvedSymbol;
if (symbol && (symbol.flags & SymbolFlags.EnumMember)) {
var declaration = symbol.valueDeclaration;
var constantValue: number;
if (declaration.kind === SyntaxKind.EnumMember && (constantValue = getNodeLinks(declaration).enumMemberValue) !== undefined) {
return constantValue.toString() + " /* " + identifierToString(declaration.name) + " */";
}
}
}
function getExportAssignmentName(node: SourceFile): string {
var symbol = getExportAssignmentSymbol(getSymbolOfNode(node));
return symbol && symbolIsValue(symbol) ? symbolToString(symbol): undefined;
@ -7335,20 +7540,51 @@ module ts {
}
function getEnumMemberValue(node: EnumMember): number {
computeEnumMemberValues(<EnumDeclaration>node.parent);
return getNodeLinks(node).enumMemberValue;
}
function getConstantValue(node: PropertyAccess): number {
var symbol = getNodeLinks(node).resolvedSymbol;
if (symbol && (symbol.flags & SymbolFlags.EnumMember)) {
var declaration = symbol.valueDeclaration;
var constantValue: number;
if (declaration.kind === SyntaxKind.EnumMember && (constantValue = getNodeLinks(declaration).enumMemberValue) !== undefined) {
return constantValue;
}
}
return undefined;
}
// Create a single instance that we can wrap the underlying emitter TextWriter with. That
// way we don't have to allocate a new wrapper every time writeTypeAtLocation and
// writeReturnTypeOfSignatureDeclaration are called.
var emitSymbolWriter = {
writer: <TextWriter>undefined,
writeKind: function (text: string) { this.writer.write(text) },
writeSymbol: function (text: string) { this.writer.write(text) },
writeLine: function () { this.writer.writeLine() },
increaseIndent: function () { this.writer.increaseIndent() },
decreaseIndent: function () { this.writer.decreaseIndent() },
clear: function () { },
trackSymbol: function (symbol: Symbol, declaration: Node, meaning: SymbolFlags) { this.writer.trackSymbol(symbol, declaration, meaning) }
};
function writeTypeAtLocation(location: Node, enclosingDeclaration: Node, flags: TypeFormatFlags, writer: TextWriter) {
// Get type of the symbol if this is the valid symbol otherwise get type at location
var symbol = getSymbolOfNode(location);
var type = symbol && !(symbol.flags & SymbolFlags.TypeLiteral) ? getTypeOfSymbol(symbol) : getTypeFromTypeNode(location);
writeTypeToTextWriter(type, enclosingDeclaration, flags, writer);
emitSymbolWriter.writer = writer;
writeType(type, emitSymbolWriter, enclosingDeclaration, flags);
}
function writeReturnTypeOfSignatureDeclaration(signatureDeclaration: SignatureDeclaration, enclosingDeclaration: Node, flags: TypeFormatFlags, writer: TextWriter) {
var signature = getSignatureFromDeclaration(signatureDeclaration);
writeTypeToTextWriter(getReturnTypeOfSignature(signature), enclosingDeclaration, flags , writer);
emitSymbolWriter.writer = writer;
writeType(getReturnTypeOfSignature(signature), emitSymbolWriter, enclosingDeclaration, flags);
}
function invokeEmitter(targetSourceFile?: SourceFile) {
@ -7356,7 +7592,6 @@ module ts {
getProgram: () => program,
getLocalNameOfContainer: getLocalNameOfContainer,
getExpressionNamePrefix: getExpressionNamePrefix,
getPropertyAccessSubstitution: getPropertyAccessSubstitution,
getExportAssignmentName: getExportAssignmentName,
isReferencedImportDeclaration: isReferencedImportDeclaration,
getNodeCheckFlags: getNodeCheckFlags,
@ -7367,9 +7602,9 @@ module ts {
isImplementationOfOverload: isImplementationOfOverload,
writeTypeAtLocation: writeTypeAtLocation,
writeReturnTypeOfSignatureDeclaration: writeReturnTypeOfSignatureDeclaration,
writeSymbol: writeSymbolToTextWriter,
isSymbolAccessible: isSymbolAccessible,
isImportDeclarationEntityNameReferenceDeclarationVisibile: isImportDeclarationEntityNameReferenceDeclarationVisibile
isImportDeclarationEntityNameReferenceDeclarationVisibile: isImportDeclarationEntityNameReferenceDeclarationVisibile,
getConstantValue: getConstantValue,
};
checkProgram();
return emitFiles(resolver, targetSourceFile);

View File

@ -101,7 +101,7 @@ module ts {
};
}
function createTextWriter(writeSymbol: (symbol: Symbol, enclosingDeclaration?: Node, meaning?: SymbolFlags)=> void): EmitTextWriter {
function createTextWriter(trackSymbol: (symbol: Symbol, enclosingDeclaration?: Node, meaning?: SymbolFlags)=> void): EmitTextWriter {
var output = "";
var indent = 0;
var lineStart = true;
@ -149,7 +149,7 @@ module ts {
return {
write: write,
writeSymbol: writeSymbol,
trackSymbol: trackSymbol,
rawWrite: rawWrite,
writeLiteral: writeLiteral,
writeLine: writeLine,
@ -182,7 +182,7 @@ module ts {
});
}
function emitComments(comments: Comment[], trailingSeparator: boolean, writer: EmitTextWriter, writeComment: (comment: Comment, writer: EmitTextWriter) => void) {
function emitComments(comments: CommentRange[], trailingSeparator: boolean, writer: EmitTextWriter, writeComment: (comment: CommentRange, writer: EmitTextWriter) => void) {
var emitLeadingSpace = !trailingSeparator;
forEach(comments, comment => {
if (emitLeadingSpace) {
@ -203,7 +203,7 @@ module ts {
});
}
function emitNewLineBeforeLeadingComments(node: TextRange, leadingComments: Comment[], writer: EmitTextWriter) {
function emitNewLineBeforeLeadingComments(node: TextRange, leadingComments: CommentRange[], writer: EmitTextWriter) {
// If the leading comments start on different line than the start of node, write new line
if (leadingComments && leadingComments.length && node.pos !== leadingComments[0].pos &&
getLineOfLocalPosition(node.pos) !== getLineOfLocalPosition(leadingComments[0].pos)) {
@ -211,7 +211,7 @@ module ts {
}
}
function writeCommentRange(comment: Comment, writer: EmitTextWriter) {
function writeCommentRange(comment: CommentRange, writer: EmitTextWriter) {
if (currentSourceFile.text.charCodeAt(comment.pos + 1) === CharacterCodes.asterisk) {
var firstCommentLineAndCharacter = currentSourceFile.getLineAndCharacterFromPosition(comment.pos);
var firstCommentLineIndent: number;
@ -307,7 +307,7 @@ module ts {
}
function emitJavaScript(jsFilePath: string, root?: SourceFile) {
var writer = createTextWriter(writeSymbol);
var writer = createTextWriter(trackSymbol);
var write = writer.write;
var writeLine = writer.writeLine;
var increaseIndent = writer.increaseIndent;
@ -363,7 +363,7 @@ module ts {
/** Sourcemap data that will get encoded */
var sourceMapData: SourceMapData;
function writeSymbol(symbol: Symbol, enclosingDeclaration: Node, meaning: SymbolFlags) { }
function trackSymbol(symbol: Symbol, enclosingDeclaration: Node, meaning: SymbolFlags) { }
function initializeEmitterWithSourceMaps() {
var sourceMapDir: string; // The directory in which sourcemap will be
@ -585,7 +585,7 @@ module ts {
sourceMapNameIndices.pop();
};
function writeCommentRangeWithMap(comment: Comment, writer: EmitTextWriter) {
function writeCommentRangeWithMap(comment: CommentRange, writer: EmitTextWriter) {
recordSourceMapSpan(comment.pos);
writeCommentRange(comment, writer);
recordSourceMapSpan(comment.end);
@ -916,14 +916,15 @@ module ts {
}
function emitPropertyAccess(node: PropertyAccess) {
var text = resolver.getPropertyAccessSubstitution(node);
if (text) {
write(text);
return;
var constantValue = resolver.getConstantValue(node);
if (constantValue !== undefined) {
write(constantValue.toString() + " /* " + identifierToString(node.right) + " */");
}
else {
emit(node.left);
write(".");
emit(node.right);
}
emit(node.left);
write(".");
emit(node.right);
}
function emitIndexedAccess(node: IndexedAccess) {
@ -2155,7 +2156,7 @@ module ts {
function getLeadingCommentsWithoutDetachedComments() {
// get the leading comments from detachedPos
var leadingComments = getLeadingComments(currentSourceFile.text, detachedCommentsInfo[detachedCommentsInfo.length - 1].detachedCommentEndPos);
var leadingComments = getLeadingCommentRanges(currentSourceFile.text, detachedCommentsInfo[detachedCommentsInfo.length - 1].detachedCommentEndPos);
if (detachedCommentsInfo.length - 1) {
detachedCommentsInfo.pop();
}
@ -2169,14 +2170,14 @@ module ts {
function getLeadingCommentsToEmit(node: Node) {
// Emit the leading comments only if the parent's pos doesn't match because parent should take care of emitting these comments
if (node.parent.kind === SyntaxKind.SourceFile || node.pos !== node.parent.pos) {
var leadingComments: Comment[];
var leadingComments: CommentRange[];
if (hasDetachedComments(node.pos)) {
// get comments without detached comments
leadingComments = getLeadingCommentsWithoutDetachedComments();
}
else {
// get the leading comments from the node
leadingComments = getLeadingCommentsOfNode(node, currentSourceFile);
leadingComments = getLeadingCommentRangesOfNode(node, currentSourceFile);
}
return leadingComments;
}
@ -2192,21 +2193,21 @@ module ts {
function emitTrailingDeclarationComments(node: Node) {
// Emit the trailing comments only if the parent's end doesn't match
if (node.parent.kind === SyntaxKind.SourceFile || node.end !== node.parent.end) {
var trailingComments = getTrailingComments(currentSourceFile.text, node.end);
var trailingComments = getTrailingCommentRanges(currentSourceFile.text, node.end);
// trailing comments are emitted at space/*trailing comment1 */space/*trailing comment*/
emitComments(trailingComments, /*trailingSeparator*/ false, writer, writeComment);
}
}
function emitLeadingCommentsOfLocalPosition(pos: number) {
var leadingComments: Comment[];
var leadingComments: CommentRange[];
if (hasDetachedComments(pos)) {
// get comments without detached comments
leadingComments = getLeadingCommentsWithoutDetachedComments();
}
else {
// get the leading comments from the node
leadingComments = getLeadingComments(currentSourceFile.text, pos);
leadingComments = getLeadingCommentRanges(currentSourceFile.text, pos);
}
emitNewLineBeforeLeadingComments({ pos: pos, end: pos }, leadingComments, writer);
// Leading comments are emitted at /*leading comment1 */space/*leading comment*/space
@ -2214,10 +2215,10 @@ module ts {
}
function emitDetachedCommentsAtPosition(node: TextRange) {
var leadingComments = getLeadingComments(currentSourceFile.text, node.pos);
var leadingComments = getLeadingCommentRanges(currentSourceFile.text, node.pos);
if (leadingComments) {
var detachedComments: Comment[] = [];
var lastComment: Comment;
var detachedComments: CommentRange[] = [];
var lastComment: CommentRange;
forEach(leadingComments, comment => {
if (lastComment) {
@ -2261,7 +2262,7 @@ module ts {
function emitPinnedOrTripleSlashCommentsOfNode(node: Node) {
var pinnedComments = ts.filter(getLeadingCommentsToEmit(node), isPinnedOrTripleSlashComment);
function isPinnedOrTripleSlashComment(comment: Comment) {
function isPinnedOrTripleSlashComment(comment: CommentRange) {
if (currentSourceFile.text.charCodeAt(comment.pos + 1) === CharacterCodes.asterisk) {
return currentSourceFile.text.charCodeAt(comment.pos + 2) === CharacterCodes.exclamation;
}
@ -2300,7 +2301,7 @@ module ts {
}
function emitDeclarations(jsFilePath: string, root?: SourceFile) {
var writer = createTextWriter(writeSymbol);
var writer = createTextWriter(trackSymbol);
var write = writer.write;
var writeLine = writer.writeLine;
var increaseIndent = writer.increaseIndent;
@ -2328,7 +2329,7 @@ module ts {
var oldWriter = writer;
forEach(importDeclarations, aliasToWrite => {
var aliasEmitInfo = forEach(aliasDeclarationEmitInfo, declEmitInfo => declEmitInfo.declaration === aliasToWrite ? declEmitInfo : undefined);
writer = createTextWriter(writeSymbol);
writer = createTextWriter(trackSymbol);
for (var declarationIndent = aliasEmitInfo.indent; declarationIndent; declarationIndent--) {
writer.increaseIndent();
}
@ -2339,10 +2340,9 @@ module ts {
writer = oldWriter;
}
function writeSymbol(symbol: Symbol, enclosingDeclaration?: Node, meaning?: SymbolFlags) {
function trackSymbol(symbol: Symbol, enclosingDeclaration?: Node, meaning?: SymbolFlags) {
var symbolAccesibilityResult = resolver.isSymbolAccessible(symbol, enclosingDeclaration, meaning);
if (symbolAccesibilityResult.accessibility === SymbolAccessibility.Accessible) {
resolver.writeSymbol(symbol, enclosingDeclaration, meaning, writer);
// write the aliases
if (symbolAccesibilityResult && symbolAccesibilityResult.aliasesToMakeVisible) {

View File

@ -138,25 +138,27 @@ module ts {
return (<Identifier>(<ExpressionStatement>node).expression).text === "use strict";
}
export function getLeadingCommentsOfNode(node: Node, sourceFileOfNode: SourceFile) {
export function getLeadingCommentRangesOfNode(node: Node, sourceFileOfNode?: SourceFile) {
sourceFileOfNode = sourceFileOfNode || getSourceFileOfNode(node);
// If parameter/type parameter, the prev token trailing comments are part of this node too
if (node.kind === SyntaxKind.Parameter || node.kind === SyntaxKind.TypeParameter) {
// e.g. (/** blah */ a, /** blah */ b);
return concatenate(getTrailingComments(sourceFileOfNode.text, node.pos),
return concatenate(getTrailingCommentRanges(sourceFileOfNode.text, node.pos),
// e.g.: (
// /** blah */ a,
// /** blah */ b);
getLeadingComments(sourceFileOfNode.text, node.pos));
getLeadingCommentRanges(sourceFileOfNode.text, node.pos));
}
else {
return getLeadingComments(sourceFileOfNode.text, node.pos);
return getLeadingCommentRanges(sourceFileOfNode.text, node.pos);
}
}
export function getJsDocComments(node: Declaration, sourceFileOfNode: SourceFile) {
return filter(getLeadingCommentsOfNode(node, sourceFileOfNode), comment => isJsDocComment(comment));
return filter(getLeadingCommentRangesOfNode(node, sourceFileOfNode), comment => isJsDocComment(comment));
function isJsDocComment(comment: Comment) {
function isJsDocComment(comment: CommentRange) {
// True if the comment starts with '/**' but not if it is '/**/'
return sourceFileOfNode.text.charCodeAt(comment.pos + 1) === CharacterCodes.asterisk &&
sourceFileOfNode.text.charCodeAt(comment.pos + 2) === CharacterCodes.asterisk &&

View File

@ -371,8 +371,8 @@ module ts {
// between the given position and the next line break are returned. The return value is an array containing a TextRange for each
// comment. Single-line comment ranges include the beginning '//' characters but not the ending line break. Multi-line comment
// ranges include the beginning '/* and ending '*/' characters. The return value is undefined if no comments were found.
function getCommentRanges(text: string, pos: number, trailing: boolean): Comment[] {
var result: Comment[];
function getCommentRanges(text: string, pos: number, trailing: boolean): CommentRange[] {
var result: CommentRange[];
var collecting = trailing || pos === 0;
while (true) {
var ch = text.charCodeAt(pos);
@ -440,11 +440,11 @@ module ts {
}
}
export function getLeadingComments(text: string, pos: number): Comment[] {
export function getLeadingCommentRanges(text: string, pos: number): CommentRange[] {
return getCommentRanges(text, pos, /*trailing*/ false);
}
export function getTrailingComments(text: string, pos: number): Comment[] {
export function getTrailingCommentRanges(text: string, pos: number): CommentRange[] {
return getCommentRanges(text, pos, /*trailing*/ true);
}

View File

@ -529,7 +529,7 @@ module ts {
filename: string;
}
export interface Comment extends TextRange {
export interface CommentRange extends TextRange {
hasTrailingNewLine?: boolean;
}
@ -640,15 +640,21 @@ module ts {
getApparentType(type: Type): ApparentType;
typeToString(type: Type, enclosingDeclaration?: Node, flags?: TypeFormatFlags): string;
symbolToString(symbol: Symbol, enclosingDeclaration?: Node, meaning?: SymbolFlags): string;
typeToDisplayParts(type: Type, enclosingDeclaration?: Node, flags?: TypeFormatFlags): SymbolDisplayPart[];
symbolToDisplayParts(symbol: Symbol, enclosingDeclaration?: Node, meaning?: SymbolFlags): SymbolDisplayPart[];
getFullyQualifiedName(symbol: Symbol): string;
getAugmentedPropertiesOfApparentType(type: Type): Symbol[];
getRootSymbol(symbol: Symbol): Symbol;
getContextualType(node: Node): Type;
// Returns the constant value of this enum member, or 'undefined' if the enum member has a
// computed value.
getEnumMemberValue(node: EnumMember): number;
}
export interface TextWriter {
write(s: string): void;
writeSymbol(symbol: Symbol, enclosingDeclaration?: Node, meaning?: SymbolFlags): void;
trackSymbol(symbol: Symbol, enclosingDeclaration?: Node, meaning?: SymbolFlags): void;
writeLine(): void;
increaseIndent(): void;
decreaseIndent(): void;
@ -679,7 +685,6 @@ module ts {
getProgram(): Program;
getLocalNameOfContainer(container: Declaration): string;
getExpressionNamePrefix(node: Identifier): string;
getPropertyAccessSubstitution(node: PropertyAccess): string;
getExportAssignmentName(node: SourceFile): string;
isReferencedImportDeclaration(node: ImportDeclaration): boolean;
isTopLevelValueImportedViaEntityName(node: ImportDeclaration): boolean;
@ -690,9 +695,12 @@ module ts {
isImplementationOfOverload(node: FunctionDeclaration): boolean;
writeTypeAtLocation(location: Node, enclosingDeclaration: Node, flags: TypeFormatFlags, writer: TextWriter): void;
writeReturnTypeOfSignatureDeclaration(signatureDeclaration: SignatureDeclaration, enclosingDeclaration: Node, flags: TypeFormatFlags, writer: TextWriter): void;
writeSymbol(symbol: Symbol, enclosingDeclaration: Node, meaning: SymbolFlags, writer: TextWriter): void;
isSymbolAccessible(symbol: Symbol, enclosingDeclaration: Node, meaning: SymbolFlags): SymbolAccessiblityResult;
isImportDeclarationEntityNameReferenceDeclarationVisibile(entityName: EntityName): SymbolAccessiblityResult;
// Returns the constant value this property access resolves to, or 'undefined' if it does
// resolve to a constant.
getConstantValue(node: PropertyAccess): number;
}
export enum SymbolFlags {
@ -794,13 +802,16 @@ module ts {
}
export enum NodeCheckFlags {
TypeChecked = 0x00000001, // Node has been type checked
LexicalThis = 0x00000002, // Lexical 'this' reference
CaptureThis = 0x00000004, // Lexical 'this' used in body
EmitExtends = 0x00000008, // Emit __extends
SuperInstance = 0x00000010, // Instance 'super' reference
SuperStatic = 0x00000020, // Static 'super' reference
ContextChecked = 0x00000040, // Contextual types have been assigned
TypeChecked = 0x00000001, // Node has been type checked
LexicalThis = 0x00000002, // Lexical 'this' reference
CaptureThis = 0x00000004, // Lexical 'this' used in body
EmitExtends = 0x00000008, // Emit __extends
SuperInstance = 0x00000010, // Instance 'super' reference
SuperStatic = 0x00000020, // Static 'super' reference
ContextChecked = 0x00000040, // Contextual types have been assigned
// Values for enum members have been computed, and any errors have been reported for them.
EnumValuesComputed = 0x00000080,
}
export interface NodeLinks {
@ -1171,6 +1182,48 @@ module ts {
verticalTab = 0x0B, // \v
}
export class SymbolDisplayPart {
constructor(public text: string,
public kind: SymbolDisplayPartKind,
public symbol: Symbol) {
}
public toJSON() {
return {
text: this.text,
kind: SymbolDisplayPartKind[this.kind]
};
}
}
export enum SymbolDisplayPartKind {
aliasName,
className,
enumName,
fieldName,
interfaceName,
keyword,
labelName,
lineBreak,
numericLiteral,
stringLiteral,
localName,
methodName,
moduleName,
namespaceName,
operator,
parameterName,
propertyName,
punctuation,
space,
anonymousTypeIndicator,
text,
typeParameterName,
enumMemberName,
functionName,
regularExpressionLiteral,
}
export interface CancellationToken {
isCancellationRequested(): boolean;
}

View File

@ -45,6 +45,7 @@ module ts {
getFlags(): SymbolFlags;
getName(): string;
getDeclarations(): Declaration[];
getDocumentationComment(): string;
}
export interface Type {
@ -96,9 +97,7 @@ module ts {
private _children: Node[];
public getSourceFile(): SourceFile {
var node: Node = this;
while (node.kind !== SyntaxKind.SourceFile) node = node.parent;
return <SourceFile>node;
return getSourceFileOfNode(this);
}
public getStart(sourceFile?: SourceFile): number {
@ -224,19 +223,176 @@ module ts {
flags: SymbolFlags;
name: string;
declarations: Declaration[];
// Undefined is used to indicate the value has not been computed. If, after computing, the
// symbol has no doc comment, then the empty string will be returned.
documentationComment: string;
constructor(flags: SymbolFlags, name: string) {
this.flags = flags;
this.name = name;
}
getFlags(): SymbolFlags {
return this.flags;
}
getName(): string {
return this.name;
}
getDeclarations(): Declaration[] {
return this.declarations;
}
getDocumentationComment(): string {
if (this.documentationComment === undefined) {
var lines: string[] = [];
// Get the doc comments from all the declarations of this symbol, and merge them
// into one single doc comment.
var declarations = this.getDeclarations();
if (declarations) {
for (var i = 0, n = declarations.length; i < n; i++) {
this.processDocumentationCommentDeclaration(lines, declarations[0]);
}
}
// TODO: get the newline info from the host.
this.documentationComment = lines.join("\r\n");
}
return this.documentationComment;
}
private processDocumentationCommentDeclaration(lines: string[], declaration: Node) {
var commentRanges = getLeadingCommentRangesOfNode(declaration);
if (commentRanges) {
var sourceFile = declaration.getSourceFile();
for (var i = 0, n = commentRanges.length; i < n; i++) {
this.processDocumentationCommentRange(
lines, sourceFile, commentRanges[0]);
}
}
}
private processDocumentationCommentRange(lines: string[], sourceFile: SourceFile, commentRange: CommentRange) {
// We only care about well-formed /** */ comments
if (commentRange.end - commentRange.pos > "/**/".length &&
sourceFile.text.substr(commentRange.pos, "/**".length) === "/**" &&
sourceFile.text.substr(commentRange.end - "*/".length, "*/".length) === "*/") {
// Put a newline between each converted comment we join together.
if (lines.length) {
lines.push("");
}
var startLineAndChar = sourceFile.getLineAndCharacterFromPosition(commentRange.pos);
var endLineAndChar = sourceFile.getLineAndCharacterFromPosition(commentRange.end);
if (startLineAndChar.line === endLineAndChar.line) {
// A single line doc comment. Just extract the text between the
// comment markers and add that to the doc comment we're building
// up.
lines.push(sourceFile.text.substring(commentRange.pos + "/**".length, commentRange.end - "*/".length).trim());
}
else {
this.processMultiLineDocumentationCommentRange(sourceFile, commentRange, startLineAndChar, endLineAndChar, lines);
}
}
}
private processMultiLineDocumentationCommentRange(
sourceFile: SourceFile, commentRange: CommentRange,
startLineAndChar: { line: number; character: number },
endLineAndChar: { line: number; character: number },
lines: string[]) {
// Comment spanned multiple lines. Find the leftmost character
// position in each line, and use that to determine what we should
// trim off, and what part of the line to keep.
// i.e. if the comment looks like:
//
// /** Foo
// * Bar
// * Baz
// */
//
// Then we'll want to add:
// Foo
// Bar
// Baz
var trimLength: number = undefined;
for (var iLine = startLineAndChar.line + 1; iLine <= endLineAndChar.line; iLine++) {
var lineStart = sourceFile.getPositionFromLineAndCharacter(iLine, /*character:*/ 1);
var lineEnd = iLine === endLineAndChar.line
? commentRange.end - "*/".length
: sourceFile.getPositionFromLineAndCharacter(iLine + 1, 1);
var docCommentTriviaLength = this.skipDocumentationCommentTrivia(sourceFile.text, lineStart, lineEnd);
if (trimLength === undefined || (docCommentTriviaLength && docCommentTriviaLength < trimLength)) {
trimLength = docCommentTriviaLength;
}
}
// Add the first line in.
var firstLine = sourceFile.text.substring(
commentRange.pos + "/**".length,
sourceFile.getPositionFromLineAndCharacter(startLineAndChar.line + 1, /*character:*/ 1)).trim();
if (firstLine !== "") {
lines.push(firstLine);
}
// For all the lines up to the last (but not including the last), add the contents
// of the line (with the length up to the
for (var iLine = startLineAndChar.line + 1; iLine < endLineAndChar.line; iLine++) {
var line = this.trimRight(sourceFile.text.substring(
sourceFile.getPositionFromLineAndCharacter(iLine, /*character*/ 1),
sourceFile.getPositionFromLineAndCharacter(iLine + 1, /*character*/ 1))).substr(trimLength);
lines.push(line);
}
// Add the last line if there is any actual text before the */
var lastLine = this.trimRight(sourceFile.text.substring(
sourceFile.getPositionFromLineAndCharacter(endLineAndChar.line, /*character:*/ 1),
commentRange.end - "*/".length)).substr(trimLength);
if (lastLine !== "") {
lines.push(lastLine);
}
}
private trimRight(val: string) {
return val.replace(/(\n|\r|\s)+$/, '');
}
private skipDocumentationCommentTrivia(text: string, lineStart: number, lineEnd: number): number {
var seenAsterisk = false;
var lineLength = lineEnd - lineStart;
for (var i = 0; i < lineLength; i++) {
var char = text.charCodeAt(i + lineStart);
if (char === CharacterCodes.asterisk && !seenAsterisk) {
// Ignore the first asterisk we see. We want to trim out the line of *'s
// commonly seen at the start of a doc comment.
seenAsterisk = true;
continue;
}
else if (isLineBreak(char)) {
// This was a blank line. Just ignore it wrt computing the leading whitespace to
// trim.
break;
}
else if (!isWhiteSpace(char)) {
// Found a real doc comment character. Keep track of it so we can determine how
// much of the doc comment leading trivia to trim off.
return i;
}
}
return undefined;
}
}
class TypeObject implements Type {
@ -494,6 +650,7 @@ module ts {
getCompletionEntryDetails(fileName: string, position: number, entryName: string): CompletionEntryDetails;
getTypeAtPosition(fileName: string, position: number): TypeInfo;
getQuickInfoAtPosition(fileName: string, position: number): QuickInfo;
getNameOrDottedNameSpan(fileName: string, startPos: number, endPos: number): TypeScript.TextSpan;
@ -651,6 +808,15 @@ module ts {
text: string;
}
export class QuickInfo {
constructor(public kind: string,
public kindModifiers: string,
public textSpan: TypeScript.TextSpan,
public displayParts: SymbolDisplayPart[],
public documentation: SymbolDisplayPart[]) {
}
}
export class TypeInfo {
constructor(
public memberName: TypeScript.MemberName,
@ -1467,11 +1633,14 @@ module ts {
var formattingRulesProvider: TypeScript.Services.Formatting.RulesProvider;
var hostCache: HostCache; // A cache of all the information about the files on the host side.
var program: Program;
// this checker is used to answer all LS questions except errors
var typeInfoResolver: TypeChecker;
// the sole purpose of this checker is to return semantic diagnostics
// creation is deferred - use getFullTypeCheckChecker to get instance
var fullTypeCheckChecker_doNotAccessDirectly: TypeChecker;
var useCaseSensitivefilenames = false;
var sourceFilesByName: Map<SourceFile> = {};
var documentRegistry = documentRegistry;
@ -1712,11 +1881,10 @@ module ts {
return undefined;
}
var declarations = symbol.getDeclarations();
return {
name: displayName,
kind: getSymbolKind(symbol),
kindModifiers: declarations ? getNodeModifiers(declarations[0]) : ScriptElementKindModifier.none
kindModifiers: getSymbolModifiers(symbol)
};
}
@ -2197,6 +2365,12 @@ module ts {
}
}
function getSymbolModifiers(symbol: Symbol): string {
return symbol && symbol.declarations && symbol.declarations.length > 0
? getNodeModifiers(symbol.declarations[0])
: ScriptElementKindModifier.none;
}
function getNodeModifiers(node: Node): string {
var flags = node.flags;
var result: string[] = [];
@ -2210,7 +2384,114 @@ module ts {
return result.length > 0 ? result.join(',') : ScriptElementKindModifier.none;
}
/// QuickInfo
function getQuickInfoAtPosition(fileName: string, position: number): QuickInfo {
synchronizeHostData();
fileName = TypeScript.switchToForwardSlashes(fileName);
var sourceFile = getSourceFile(fileName);
var node = getNodeAtPosition(sourceFile, position);
if (!node) {
return undefined;
}
var symbol = typeInfoResolver.getSymbolInfo(node);
if (!symbol) {
return undefined;
}
var documentation = symbol.getDocumentationComment();
var documentationParts = documentation === "" ? [] : [new SymbolDisplayPart(documentation, SymbolDisplayPartKind.text, /*symbol:*/ null)];
// Having all this logic here is pretty unclean. Consider moving to the roslyn model
// where all symbol display logic is encapsulated into visitors and options.
var totalParts: SymbolDisplayPart[] = [];
if (symbol.flags & SymbolFlags.Class) {
totalParts.push(new SymbolDisplayPart("class", SymbolDisplayPartKind.keyword, undefined));
totalParts.push(new SymbolDisplayPart(" ", SymbolDisplayPartKind.space, undefined));
totalParts.push.apply(totalParts, typeInfoResolver.symbolToDisplayParts(symbol, sourceFile));
}
else if (symbol.flags & SymbolFlags.Interface) {
totalParts.push(new SymbolDisplayPart("interface", SymbolDisplayPartKind.keyword, undefined));
totalParts.push(new SymbolDisplayPart(" ", SymbolDisplayPartKind.space, undefined));
totalParts.push.apply(totalParts, typeInfoResolver.symbolToDisplayParts(symbol, sourceFile));
}
else if (symbol.flags & SymbolFlags.Enum) {
totalParts.push(new SymbolDisplayPart("enum", SymbolDisplayPartKind.keyword, undefined));
totalParts.push(new SymbolDisplayPart(" ", SymbolDisplayPartKind.space, undefined));
totalParts.push.apply(totalParts, typeInfoResolver.symbolToDisplayParts(symbol, sourceFile));
}
else if (symbol.flags & SymbolFlags.Module) {
totalParts.push(new SymbolDisplayPart("module", SymbolDisplayPartKind.keyword, undefined));
totalParts.push(new SymbolDisplayPart(" ", SymbolDisplayPartKind.space, undefined));
totalParts.push.apply(totalParts, typeInfoResolver.symbolToDisplayParts(symbol, sourceFile));
}
else if (symbol.flags & SymbolFlags.TypeParameter) {
totalParts.push(new SymbolDisplayPart("(", SymbolDisplayPartKind.punctuation, undefined));
totalParts.push(new SymbolDisplayPart("type parameter", SymbolDisplayPartKind.text, undefined));
totalParts.push(new SymbolDisplayPart(")", SymbolDisplayPartKind.punctuation, undefined));
totalParts.push(new SymbolDisplayPart(" ", SymbolDisplayPartKind.space, undefined));
totalParts.push.apply(totalParts, typeInfoResolver.symbolToDisplayParts(symbol));
}
else {
totalParts.push(new SymbolDisplayPart("(", SymbolDisplayPartKind.punctuation, undefined));
var text: string;
if (symbol.flags & SymbolFlags.Property) { text = "property" }
else if (symbol.flags & SymbolFlags.EnumMember) { text = "enum member" }
else if (symbol.flags & SymbolFlags.Function) { text = "function" }
else if (symbol.flags & SymbolFlags.Variable) { text = "variable" }
else if (symbol.flags & SymbolFlags.Method) { text = "method" }
if (!text) {
return undefined;
}
totalParts.push(new SymbolDisplayPart(text, SymbolDisplayPartKind.text, undefined));
totalParts.push(new SymbolDisplayPart(")", SymbolDisplayPartKind.punctuation, undefined));
totalParts.push(new SymbolDisplayPart(" ", SymbolDisplayPartKind.space, undefined));
totalParts.push.apply(totalParts, typeInfoResolver.symbolToDisplayParts(symbol, getContainerNode(node)));
var type = typeInfoResolver.getTypeOfSymbol(symbol);
if (symbol.flags & SymbolFlags.Property ||
symbol.flags & SymbolFlags.Variable) {
if (type) {
totalParts.push(new SymbolDisplayPart(":", SymbolDisplayPartKind.punctuation, undefined));
totalParts.push(new SymbolDisplayPart(" ", SymbolDisplayPartKind.space, undefined));
totalParts.push.apply(totalParts, typeInfoResolver.typeToDisplayParts(type, getContainerNode(node)));
}
}
else if (symbol.flags & SymbolFlags.Function ||
symbol.flags & SymbolFlags.Method) {
if (type) {
totalParts.push.apply(totalParts, typeInfoResolver.typeToDisplayParts(type, getContainerNode(node)));
}
}
else if (symbol.flags & SymbolFlags.EnumMember) {
var declaration = symbol.declarations[0];
if (declaration.kind === SyntaxKind.EnumMember) {
var constantValue = typeInfoResolver.getEnumMemberValue(<EnumMember>declaration);
if (constantValue !== undefined) {
totalParts.push(new SymbolDisplayPart(" ", SymbolDisplayPartKind.space, undefined));
totalParts.push(new SymbolDisplayPart("=", SymbolDisplayPartKind.operator, undefined));
totalParts.push(new SymbolDisplayPart(" ", SymbolDisplayPartKind.space, undefined));
totalParts.push(new SymbolDisplayPart(constantValue.toString(), SymbolDisplayPartKind.numericLiteral, undefined));
}
}
}
}
return new QuickInfo(
getSymbolKind(symbol),
getSymbolModifiers(symbol),
new TypeScript.TextSpan(node.getStart(), node.getWidth()),
totalParts,
documentationParts);
}
function getTypeAtPosition(fileName: string, position: number): TypeInfo {
synchronizeHostData();
@ -3926,8 +4207,8 @@ module ts {
}
// Looks to be within the trivia. See if we can find the comment containing it.
if (!getContainingComment(getTrailingComments(fileContents, token.getFullStart()), matchPosition) &&
!getContainingComment(getLeadingComments(fileContents, token.getFullStart()), matchPosition)) {
if (!getContainingComment(getTrailingCommentRanges(fileContents, token.getFullStart()), matchPosition) &&
!getContainingComment(getLeadingCommentRanges(fileContents, token.getFullStart()), matchPosition)) {
continue;
}
@ -4014,7 +4295,7 @@ module ts {
return new RegExp(regExpString, "gim");
}
function getContainingComment(comments: Comment[], position: number): Comment {
function getContainingComment(comments: CommentRange[], position: number): CommentRange {
if (comments) {
for (var i = 0, n = comments.length; i < n; i++) {
var comment = comments[i];
@ -4052,7 +4333,7 @@ module ts {
var kind = getSymbolKind(symbol);
if (kind) {
return RenameInfo.Create(symbol.name, typeInfoResolver.getFullyQualifiedName(symbol), kind,
getNodeModifiers(symbol.getDeclarations()[0]),
getSymbolModifiers(symbol),
new TypeScript.TextSpan(node.getStart(), node.getWidth()));
}
}
@ -4072,6 +4353,7 @@ module ts {
getCompletionsAtPosition: getCompletionsAtPosition,
getCompletionEntryDetails: getCompletionEntryDetails,
getTypeAtPosition: getTypeAtPosition,
getQuickInfoAtPosition: getQuickInfoAtPosition,
getSignatureHelpItems: (filename, position): SignatureHelpItems => null,
getSignatureHelpCurrentArgumentState: (fileName, position, applicableSpanStart): SignatureHelpState => null,
getDefinitionAtPosition: getDefinitionAtPosition,

View File

@ -87,7 +87,9 @@ module ts {
getCompletionsAtPosition(fileName: string, position: number, isMemberCompletion: boolean): string;
getCompletionEntryDetails(fileName: string, position: number, entryName: string): string;
getQuickInfoAtPosition(fileName: string, position: number): string;
getTypeAtPosition(fileName: string, position: number): string;
getNameOrDottedNameSpan(fileName: string, startPos: number, endPos: number): string;
getBreakpointStatementAtPosition(fileName: string, position: number): string;
@ -540,6 +542,16 @@ module ts {
/// QUICKINFO
/// Computes a string representation of the type at the requested position
/// in the active file.
public getQuickInfoAtPosition(fileName: string, position: number): string {
return this.forwardJSONCall(
"getQuickInfoAtPosition('" + fileName + "', " + position + ")",
() => {
var quickInfo = this.languageService.getQuickInfoAtPosition(fileName, position);
return quickInfo;
});
}
public getTypeAtPosition(fileName: string, position: number): string {
return this.forwardJSONCall(
"getTypeAtPosition('" + fileName + "', " + position + ")",