diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 625f4d75eb..d650865ef7 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -2208,6 +2208,8 @@ namespace ts { let encounteredError = false; let inObjectTypeLiteral = false; let checkAlias = true; + let enclosingDeclaration: Node = undefined; // TODO: add parameter. + let symbolStack: Symbol[] = undefined; let result = createTypeNodeWorker(type); if (result) { @@ -2224,7 +2226,6 @@ namespace ts { const typeString = typeToString(type); typeString; // TODO: remove. - if (type.flags & TypeFlags.Any) { // TODO: add other case where type ends up being `any`. return createKeywordTypeNode(SyntaxKind.AnyKeyword); @@ -2239,7 +2240,7 @@ namespace ts { return createKeywordTypeNode(SyntaxKind.BooleanKeyword); } if (type.flags & TypeFlags.Enum) { - throw new Error("enum not implemented"); + throw new Error ("enums not implemented") } if (type.flags & (TypeFlags.StringLiteral)) { return createLiteralTypeNode((createLiteral((type).text))); @@ -2316,6 +2317,7 @@ namespace ts { if (type.flags & TypeFlags.Union) { return createUnionOrIntersectionTypeNode(SyntaxKind.UnionType, mapToTypeNodeArray(formatUnionTypes((type).types))); } + if (type.flags & TypeFlags.Intersection) { return createUnionOrIntersectionTypeNode(SyntaxKind.IntersectionType, mapToTypeNodeArray((type as UnionType).types)); } @@ -2336,7 +2338,7 @@ namespace ts { return createMappedTypeNode(readonlyToken, typeParameterNode, questionToken, templateTypeNode); } - if (objectFlags & ObjectFlags.Anonymous) { + if (objectFlags & (ObjectFlags.Anonymous | ObjectFlags.Mapped)) { Debug.assert(!!(type.flags & TypeFlags.Object)); // The type is an object literal type. if (!type.symbol) { @@ -2346,18 +2348,20 @@ namespace ts { noop(); } - return createTypeLiteralNodeFromType(type); + return createAnonymousTypeNode(type); } - // TODO: string or number literal here or above? - if (type.flags & TypeFlags.Index) { - // TODO: implement and test. - throw new Error("index not implemented"); + // TODO: test. + const indexType = getIndexType(getApparentType((type).type)); + const indexTypeNode = createTypeNodeWorker(indexType); + return createTypeOperatorNode(indexTypeNode); } if (type.flags & TypeFlags.IndexedAccess) { - // TODO: implement and test. - throw new Error("indexed access not implemented"); + // TODO: test. + const objectTypeNode = createTypeNodeWorker((type).objectType); + const indexTypeNode = createTypeNodeWorker((type).indexType); + return createIndexedAccessTypeNode(objectTypeNode, indexTypeNode); } Debug.fail("Should be unreachable."); @@ -2367,6 +2371,214 @@ namespace ts { return asNodeArray(types && types.map(createTypeNodeWorker) as TypeNode[]); } + /******** START COPY *********/ + + // function buildTypeDisplay(type: Type, writer: SymbolWriter, enclosingDeclaration?: Node, globalFlags?: TypeFormatFlags, symbolStack?: Symbol[]) { + // const globalFlagsToPass = globalFlags & TypeFormatFlags.WriteOwnNameForAnyLike; + // let inObjectTypeLiteral = false; + // return writeType(type, globalFlags); + + // function writeType(type: Type, flags: TypeFormatFlags) { + // // const nextFlags = flags & ~TypeFormatFlags.InTypeAlias; + // // // Write undefined/null type as any + // // if (type.flags & TypeFlags.Intrinsic) { + // // // Special handling for unknown / resolving types, they should show up as any and not unknown or __resolving + // // writer.writeKeyword(!(globalFlags & TypeFormatFlags.WriteOwnNameForAnyLike) && isTypeAny(type) + // // ? "any" + // // : (type).intrinsicName); + // // } + // // else if (type.flags & TypeFlags.TypeParameter && (type as TypeParameter).isThisType) { + // // if (inObjectTypeLiteral) { + // // writer.reportInaccessibleThisError(); + // // } + // // writer.writeKeyword("this"); + // // } + // else if (getObjectFlags(type) & ObjectFlags.Reference) { + // writeTypeReference(type, nextFlags); + // } + // else if (type.flags & TypeFlags.EnumLiteral) { + // buildSymbolDisplay(getParentOfSymbol(type.symbol), writer, enclosingDeclaration, SymbolFlags.Type, SymbolFormatFlags.None, nextFlags); + // writePunctuation(writer, SyntaxKind.DotToken); + // appendSymbolNameOnly(type.symbol, writer); + // } + // else if (getObjectFlags(type) & ObjectFlags.ClassOrInterface || type.flags & (TypeFlags.Enum | TypeFlags.TypeParameter)) { + // // The specified symbol flags need to be reinterpreted as type flags + // buildSymbolDisplay(type.symbol, writer, enclosingDeclaration, SymbolFlags.Type, SymbolFormatFlags.None, nextFlags); + // } + // else if (!(flags & TypeFormatFlags.InTypeAlias) && type.aliasSymbol && + // isSymbolAccessible(type.aliasSymbol, enclosingDeclaration, SymbolFlags.Type, /*shouldComputeAliasesToMakeVisible*/ false).accessibility === SymbolAccessibility.Accessible) { + // const typeArguments = type.aliasTypeArguments; + // writeSymbolTypeReference(type.aliasSymbol, typeArguments, 0, length(typeArguments), nextFlags); + // } + // else if (type.flags & TypeFlags.UnionOrIntersection) { + // writeUnionOrIntersectionType(type, nextFlags); + // } + // else if (getObjectFlags(type) & (ObjectFlags.Anonymous | ObjectFlags.Mapped)) { + // writeAnonymousType(type, nextFlags); + // } + // else if (type.flags & TypeFlags.StringOrNumberLiteral) { + // writer.writeStringLiteral(literalTypeToString(type)); + // } + // } + + /******** END COPY *********/ + + function createAnonymousTypeNode(type: ObjectType): TypeNode { + const symbol = type.symbol; + if (symbol) { + // Always use 'typeof T' for type of class, enum, and module objects + if (symbol.flags & SymbolFlags.Class && !getBaseTypeVariableOfClass(symbol) || + symbol.flags & (SymbolFlags.Enum | SymbolFlags.ValueModule) || + shouldWriteTypeOfFunctionSymbol()) { + // TODO: test. + // TODO: get entity name from symbol. + return createTypeQueryNodeFromType(type); + } + else if (contains(symbolStack, symbol)) { + // If type is an anonymous type literal in a type alias declaration, use type alias name + const typeAlias = getTypeAliasForTypeLiteral(type); + if (typeAlias) { + // The specified symbol flags need to be reinterpreted as type flags + const entityName = getEntityNameFromSymbol(typeAlias, enclosingDeclaration); + return createTypeReferenceNode(entityName, /*typeArguments*/ undefined); + } + else { + return createKeywordTypeNode(SyntaxKind.AnyKeyword); + } + } + else { + // Since instantiations of the same anonymous type have the same symbol, tracking symbols instead + // of types allows us to catch circular references to instantiations of the same anonymous type + if (!symbolStack) { + symbolStack = []; + } + symbolStack.push(symbol); + let result = createTypeLiteralNodeFromType(type); + symbolStack.pop(); + return result; + } + } + else { + // Anonymous types with no symbol are never circular + return createTypeLiteralNodeFromType(type); + } + + function shouldWriteTypeOfFunctionSymbol() { + const isStaticMethodSymbol = !!(symbol.flags & SymbolFlags.Method && // typeof static method + forEach(symbol.declarations, declaration => getModifierFlags(declaration) & ModifierFlags.Static)); + const isNonLocalFunctionSymbol = !!(symbol.flags & SymbolFlags.Function) && + (symbol.parent || // is exported function symbol + forEach(symbol.declarations, declaration => + declaration.parent.kind === SyntaxKind.SourceFile || declaration.parent.kind === SyntaxKind.ModuleBlock)); + if (isStaticMethodSymbol || isNonLocalFunctionSymbol) { + // typeof is allowed only for static/non local functions + return contains(symbolStack, symbol); // it is type of the symbol uses itself recursively + } + } + } + + function writeLiteralType(type: ObjectType, flags: TypeFormatFlags) { + if (type.objectFlags & ObjectFlags.Mapped) { + if (getConstraintTypeFromMappedType(type).flags & (TypeFlags.TypeParameter | TypeFlags.Index)) { + writeMappedType(type); + return; + } + } + + const resolved = resolveStructuredTypeMembers(type); + if (!resolved.properties.length && !resolved.stringIndexInfo && !resolved.numberIndexInfo) { + if (!resolved.callSignatures.length && !resolved.constructSignatures.length) { + writePunctuation(writer, SyntaxKind.OpenBraceToken); + writePunctuation(writer, SyntaxKind.CloseBraceToken); + return; + } + + if (resolved.callSignatures.length === 1 && !resolved.constructSignatures.length) { + const parenthesizeSignature = shouldAddParenthesisAroundFunctionType(resolved.callSignatures[0], flags); + if (parenthesizeSignature) { + writePunctuation(writer, SyntaxKind.OpenParenToken); + } + buildSignatureDisplay(resolved.callSignatures[0], writer, enclosingDeclaration, globalFlagsToPass | TypeFormatFlags.WriteArrowStyleSignature, /*kind*/ undefined, symbolStack); + if (parenthesizeSignature) { + writePunctuation(writer, SyntaxKind.CloseParenToken); + } + return; + } + if (resolved.constructSignatures.length === 1 && !resolved.callSignatures.length) { + if (flags & TypeFormatFlags.InElementType) { + writePunctuation(writer, SyntaxKind.OpenParenToken); + } + writeKeyword(writer, SyntaxKind.NewKeyword); + writeSpace(writer); + buildSignatureDisplay(resolved.constructSignatures[0], writer, enclosingDeclaration, globalFlagsToPass | TypeFormatFlags.WriteArrowStyleSignature, /*kind*/ undefined, symbolStack); + if (flags & TypeFormatFlags.InElementType) { + writePunctuation(writer, SyntaxKind.CloseParenToken); + } + return; + } + } + + const saveInObjectTypeLiteral = inObjectTypeLiteral; + inObjectTypeLiteral = true; + writePunctuation(writer, SyntaxKind.OpenBraceToken); + writer.writeLine(); + writer.increaseIndent(); + writeObjectLiteralType(resolved); + writer.decreaseIndent(); + writePunctuation(writer, SyntaxKind.CloseBraceToken); + inObjectTypeLiteral = saveInObjectTypeLiteral; + } + + function writeObjectLiteralType(resolved: ResolvedType) { + for (const signature of resolved.callSignatures) { + buildSignatureDisplay(signature, writer, enclosingDeclaration, globalFlagsToPass, /*kind*/ undefined, symbolStack); + writePunctuation(writer, SyntaxKind.SemicolonToken); + writer.writeLine(); + } + for (const signature of resolved.constructSignatures) { + buildSignatureDisplay(signature, writer, enclosingDeclaration, globalFlagsToPass, SignatureKind.Construct, symbolStack); + writePunctuation(writer, SyntaxKind.SemicolonToken); + writer.writeLine(); + } + buildIndexSignatureDisplay(resolved.stringIndexInfo, writer, IndexKind.String, enclosingDeclaration, globalFlags, symbolStack); + buildIndexSignatureDisplay(resolved.numberIndexInfo, writer, IndexKind.Number, enclosingDeclaration, globalFlags, symbolStack); + for (const p of resolved.properties) { + const t = getTypeOfSymbol(p); + if (p.flags & (SymbolFlags.Function | SymbolFlags.Method) && !getPropertiesOfObjectType(t).length) { + const signatures = getSignaturesOfType(t, SignatureKind.Call); + for (const signature of signatures) { + writePropertyWithModifiers(p); + buildSignatureDisplay(signature, writer, enclosingDeclaration, globalFlagsToPass, /*kind*/ undefined, symbolStack); + writePunctuation(writer, SyntaxKind.SemicolonToken); + writer.writeLine(); + } + } + else { + writePropertyWithModifiers(p); + writePunctuation(writer, SyntaxKind.ColonToken); + writeSpace(writer); + writeType(t, TypeFormatFlags.None); + writePunctuation(writer, SyntaxKind.SemicolonToken); + writer.writeLine(); + } + } + } + + function createTypeQueryNodeFromType(type: Type) { + const symbol = type.symbol; + if (symbol) { + // TODO: get entity name instead. + const entityName = createIdentifier(symbolToString(symbol)); + return createTypeQueryNode(entityName); + } + } + + function getEntityNameFromSymbol(symbol: Symbol, enclosingDeclaration: Node): EntityName { + symbol; enclosingDeclaration; + // TODO: actually implement this + return createIdentifier(symbolToString(symbol, enclosingDeclaration)); + } + function createTypeReferenceNodeFromType(type: TypeReference) { const typeArguments: Type[] = type.typeArguments || emptyArray; if (type.target === globalArrayType) { @@ -2778,6 +2990,7 @@ namespace ts { } } + function writeTypeList(types: Type[], delimiter: SyntaxKind) { for (let i = 0; i < types.length; i++) { if (i > 0) { diff --git a/src/compiler/factory.ts b/src/compiler/factory.ts index c9fef5b026..ebb4bbe5a9 100644 --- a/src/compiler/factory.ts +++ b/src/compiler/factory.ts @@ -255,25 +255,8 @@ namespace ts { : node; } - export function createSignatureDeclaration(kind: SyntaxKind, name: string | PropertyName | undefined, typeParameters: NodeArray | undefined, parameters: NodeArray, type: TypeNode | undefined): T { - const signatureDeclaration = createSynthesizedNode(kind) as T; - signatureDeclaration.name = asName(name); - signatureDeclaration.typeParameters = asNodeArray(typeParameters); - signatureDeclaration.type = type; - return signatureDeclaration; - } - - export function updateSignatureDeclaration(node: T, name: string | PropertyName | undefined, typeParameters: NodeArray | undefined, parameters: NodeArray, type: TypeNode | undefined): T { - return node.name !== name - || node.typeParameters !== typeParameters - || node.type !== type - ? updateNode(createSignatureDeclaration(node.kind, name, typeParameters, parameters, type), node) - : node; - } - export function createTypeReferenceNode(typeName: string | EntityName, typeArguments: NodeArray | undefined) { const typeReference = createSynthesizedNode(SyntaxKind.TypeReference) as TypeReferenceNode; - typeReference.typeName = isQualifiedName(typeName) ? typeName : asName(typeName); typeReference.typeArguments = typeArguments; return typeReference; @@ -286,6 +269,16 @@ namespace ts { : node; } + export function createTypeQueryNode(exprName: EntityName) { + const typeQueryNode = createSynthesizedNode(SyntaxKind.TypeQuery) as TypeQueryNode; + typeQueryNode.exprName = exprName; + return typeQueryNode; + } + + export function updateTypeQueryNode(node: TypeQueryNode, exprName: EntityName) { + return node.exprName !== exprName ? updateNode(createTypeQueryNode(exprName) , node) : node; + } + export function createArrayTypeNode(elementType: TypeNode): ArrayTypeNode { const arrayTypeNode = createSynthesizedNode(SyntaxKind.ArrayType) as ArrayTypeNode; arrayTypeNode.elementType = elementType; @@ -355,6 +348,33 @@ namespace ts { : node; } + export function createTypeOperatorNode(type: TypeNode) { + const typeOperatorNode = createSynthesizedNode(SyntaxKind.TypeOperator) as TypeOperatorNode; + typeOperatorNode.operator = SyntaxKind.KeyOfKeyword; + typeOperatorNode.type = type + return typeOperatorNode; + } + + export function updateTypeOperatorNode(node: TypeOperatorNode, type: TypeNode) { + return node.type !== type ? updateNode(createTypeOperatorNode(type), node) : node; + } + + export function createIndexedAccessTypeNode(objectType: TypeNode, indexType: TypeNode) { + const indexedAccessTypeNode = createSynthesizedNode(SyntaxKind.IndexedAccessType) as IndexedAccessTypeNode; + indexedAccessTypeNode.objectType = objectType; + indexedAccessTypeNode.indexType = indexType; + return indexedAccessTypeNode; + } + + + export function updateIndexedAccessTypeNode(node: IndexedAccessTypeNode, objectType: TypeNode, indexType: TypeNode) { + return node.objectType !== objectType + || node.indexType !== indexType + ? updateNode(createIndexedAccessTypeNode(objectType, indexType), node) + : node; + } + + // Type Declarations export function createTypeParameterDeclaration(name: string | Identifier, constraint: TypeNode | undefined, defaultParameter: TypeNode | undefined) { @@ -374,6 +394,24 @@ namespace ts { : node; } + export function createSignatureDeclaration(kind: SyntaxKind, name: string | PropertyName | undefined, typeParameters: NodeArray | undefined, parameters: NodeArray, type: TypeNode | undefined): T { + const signatureDeclaration = createSynthesizedNode(kind) as T; + signatureDeclaration.name = asName(name); + signatureDeclaration.typeParameters = asNodeArray(typeParameters); + signatureDeclaration.parameters = asNodeArray(parameters); + signatureDeclaration.type = type; + return signatureDeclaration; + } + + export function updateSignatureDeclaration(node: T, name: string | PropertyName | undefined, typeParameters: NodeArray | undefined, parameters: NodeArray, type: TypeNode | undefined): T { + return node.name !== name + || node.typeParameters !== typeParameters + || node.parameters !== parameters + || node.type !== type + ? updateNode(createSignatureDeclaration(node.kind, name, typeParameters, parameters, type), node) + : node; + } + // Signature elements export function createPropertySignature(name: PropertyName, questionToken: QuestionToken | undefined, type: TypeNode | undefined, initializer: Expression | undefined): PropertySignature { diff --git a/src/compiler/visitor.ts b/src/compiler/visitor.ts index 206e269f36..afe236859a 100644 --- a/src/compiler/visitor.ts +++ b/src/compiler/visitor.ts @@ -320,13 +320,13 @@ namespace ts { case SyntaxKind.ConstructorType: throw new Error("reached unsupported type in visitor."); case SyntaxKind.TypeQuery: - throw new Error("reached unsupported type in visitor."); + return updateTypeQueryNode((node), visitNode((node).exprName, visitor, isEntityName)); case SyntaxKind.TypeLiteral: return updateTypeLiteralNode((node), nodesVisitor((node).members, visitor)); case SyntaxKind.ArrayType: return updateArrayTypeNode(node, visitNode((node).elementType, visitor, isTypeNode)); case SyntaxKind.TupleType: - throw new Error("reached unsupported type in visitor."); + return updateTypleTypeNode((node), nodesVisitor((node).elementTypes, visitor, isTypeNode)); case SyntaxKind.UnionType: case SyntaxKind.IntersectionType: return updateUnionOrIntersectionTypeNode(node @@ -336,9 +336,11 @@ namespace ts { case SyntaxKind.ThisType: throw new Error("reached unsupported type in visitor."); case SyntaxKind.TypeOperator: - throw new Error("reached unsupported type in visitor."); + return updateTypeOperatorNode(node, visitNode((node).type, visitor, isTypeNode)); case SyntaxKind.IndexedAccessType: - throw new Error("reached unsupported type in visitor."); + return updateIndexedAccessTypeNode((node) + , visitNode((node).objectType, visitor, isTypeNode) + , visitNode((node).indexType, visitor, isTypeNode)); case SyntaxKind.MappedType: throw new Error("reached unsupported type in visitor."); case SyntaxKind.LiteralType: diff --git a/tests/cases/fourslash/codeFixClassImplementInterfaceCallSignature.ts b/tests/cases/fourslash/codeFixClassImplementInterfaceCallSignature.ts new file mode 100644 index 0000000000..4b47268396 --- /dev/null +++ b/tests/cases/fourslash/codeFixClassImplementInterfaceCallSignature.ts @@ -0,0 +1,10 @@ +/// + +//// interface I { +//// (x: number, b: string): number; +//// } +//// class C implements I {[| |]} + +verify.not.codeFixAvailable(); + + diff --git a/tests/cases/fourslash/codeFixClassImplementInterfaceConstructSignature.ts b/tests/cases/fourslash/codeFixClassImplementInterfaceConstructSignature.ts new file mode 100644 index 0000000000..1700f7ec88 --- /dev/null +++ b/tests/cases/fourslash/codeFixClassImplementInterfaceConstructSignature.ts @@ -0,0 +1,10 @@ +/// + +//// interface I { +//// new (x: number, b: string); +//// } +//// class C implements I {[| |]} + +verify.not.codeFixAvailable(); + + diff --git a/tests/cases/fourslash/codeFixClassImplementInterfacePropertyCallSignature.ts b/tests/cases/fourslash/codeFixClassImplementInterfacePropertyCallSignature.ts new file mode 100644 index 0000000000..7f0eddfb14 --- /dev/null +++ b/tests/cases/fourslash/codeFixClassImplementInterfacePropertyCallSignature.ts @@ -0,0 +1,13 @@ +/// + +//// interface I { +//// a1: { (b1: number, c1: string): number; }; +//// a2: (b2: number, c2: string) => number; +//// } +//// class C implements I {[| |]} + +verify.rangeAfterCodeFix(` + a1: (b1: number, c1: string) => number; + a2: (b2: number, c2: string) => number; +`); + diff --git a/tests/cases/fourslash/codeFixClassImplementInterfacePropertyConstructSignature.1.ts b/tests/cases/fourslash/codeFixClassImplementInterfacePropertyConstructSignature.1.ts new file mode 100644 index 0000000000..2b27fab376 --- /dev/null +++ b/tests/cases/fourslash/codeFixClassImplementInterfacePropertyConstructSignature.1.ts @@ -0,0 +1,12 @@ +/// + +//// interface I { +//// a1: { new (b1: number, c1: string): number; }; +//// a2: new (b2: number, c2: string) => number; +//// } +//// class C implements I {[| |]} + +verify.rangeAfterCodeFix(` + a1: new (b1: number, c1: string) => number; + a2: new (b2: number, c2: string) => number; +`); diff --git a/tests/cases/fourslash/codeFixClassImplementInterfacePropertyMethodSignature.ts b/tests/cases/fourslash/codeFixClassImplementInterfacePropertyMethodSignature.ts new file mode 100644 index 0000000000..3d3505e1d6 --- /dev/null +++ b/tests/cases/fourslash/codeFixClassImplementInterfacePropertyMethodSignature.ts @@ -0,0 +1,11 @@ +/// + +//// interface I { +//// x: { a(b: number, c: string): number }; +//// } +//// class C implements I {[| |]} + +verify.rangeAfterCodeFix(` + a: { (b: number, c: string): number; }; +`); +