diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 953bd3de49a..227e99b712d 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -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((type).intrinsicName); + writer.writeKind((type).intrinsicName, SymbolDisplayPartKind.keyword); } else if (type.flags & TypeFlags.Reference) { writeTypeReference(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(type); @@ -1003,18 +1160,24 @@ module ts { writeAnonymousType(type, allowFunctionOrConstructorTypeLiteral); } else if (type.flags & TypeFlags.StringLiteral) { - writer.write((type).text); + writer.writeKind((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 || (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 = 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(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: 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); diff --git a/src/compiler/emitter.ts b/src/compiler/emitter.ts index 0377fbf71bb..74b0533e5b4 100644 --- a/src/compiler/emitter.ts +++ b/src/compiler/emitter.ts @@ -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) { diff --git a/src/compiler/parser.ts b/src/compiler/parser.ts index 62898664628..7aedd585a7e 100644 --- a/src/compiler/parser.ts +++ b/src/compiler/parser.ts @@ -138,25 +138,27 @@ module ts { return ((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 && diff --git a/src/compiler/scanner.ts b/src/compiler/scanner.ts index f627bee6c51..81d16b5f487 100644 --- a/src/compiler/scanner.ts +++ b/src/compiler/scanner.ts @@ -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); } diff --git a/src/compiler/types.ts b/src/compiler/types.ts index d9c2a420fb6..259e9f68133 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -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; } diff --git a/src/services/services.ts b/src/services/services.ts index 819dcc8d825..8282677ffd4 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -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 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 = {}; 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(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, diff --git a/src/services/shims.ts b/src/services/shims.ts index 210a458b0be..a1767bf0c34 100644 --- a/src/services/shims.ts +++ b/src/services/shims.ts @@ -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 + ")",