diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 960a36e3476..58186560e8b 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -2203,13 +2203,12 @@ namespace ts { return createTypeParameterDeclaration(name, constraint, defaultParameter); } - // TODO: enclosing declaration appears to be unused in getTypeOfSymbolAtLocation function createParameterDeclarationFromSymbol(parameterSymbol: Symbol, enclosingDeclaration: Node): ParameterDeclaration { const parameterDeclaration = parameterSymbol.declarations[0] as ParameterDeclaration; const parameterType = getTypeOfSymbol(parameterSymbol); const parameterTypeNode = createTypeNode(parameterType, enclosingDeclaration); - // TODO: clone binding names correctly. - // TODO: copy initialzer in a way that checks whether all symbols used in expression are accessible here, and qualify them appropriately. + // TODO: how should we clone members/modifiers? + // TODO: check initializer accessibility correctly. const parameterNode = createParameter( parameterDeclaration.decorators && parameterDeclaration.decorators.map(getSynthesizedDeepClone) , parameterDeclaration.modifiers && parameterDeclaration.modifiers.map(getSynthesizedDeepClone) @@ -2217,11 +2216,10 @@ namespace ts { , getSynthesizedDeepClone(parameterDeclaration.name) , parameterDeclaration.questionToken && createToken(SyntaxKind.QuestionToken) , parameterTypeNode - , /*initializer*/ undefined); + , parameterDeclaration.initializer && getSynthesizedDeepClone(parameterDeclaration.initializer)); return parameterNode; } - // TODO: expose this, remove copy from helper, possibly don't expose createParameter/TypeParameter? function createSignatureParts(signature: Signature, enclosingDeclaration: Node): SignatureParts { return { typeParameters: signature.typeParameters && signature.typeParameters.map(parameter => createTypeParameterDeclarationFromType(parameter,enclosingDeclaration)), @@ -2242,12 +2240,8 @@ namespace ts { let checkAlias = true; let symbolStack: Symbol[] = undefined; - let result = createTypeNodeWorker(type); - if (result) { - (result).__type_source = type; - (result).__type_source_str = typeToString(type); - } - return result; + const result = createTypeNodeWorker(type); + return encounteredError ? undefined: result; function createTypeNodeWorker(type: Type): TypeNode { if (!type) { @@ -2255,10 +2249,7 @@ namespace ts { return undefined; } - const typeString = typeToString(type, enclosingDeclaration); typeString; // TODO: remove. - if (type.flags & TypeFlags.Any) { - // TODO: add other case where type ends up being `any`. return createKeywordTypeNode(SyntaxKind.AnyKeyword); } if (type.flags & TypeFlags.String) { @@ -2318,19 +2309,16 @@ namespace ts { } if (objectFlags & ObjectFlags.ClassOrInterface) { Debug.assert(!!(type.flags & TypeFlags.Object)); - // TODO: Detect whether class is named and fail if not. const name = createNameFromSymbol(type.symbol); // TODO: handle type arguments. - return createTypeReferenceNode(name, /*typeParameters*/undefined); + return createTypeReferenceNode(name, /*typeArguments*/ undefined); } if (type.flags & TypeFlags.TypeParameter) { - // TODO: get qualified name when necessary instead of string. const name = createNameFromSymbol(type.symbol); // Ignore constraint/default when creating a usage (as opposed to declaration) of a type parameter. return createTypeReferenceNode(name, /*typeArguments*/ undefined); } - // TODO: move back up later on? if (checkAlias && type.aliasSymbol) { const name = createNameFromSymbol(type.aliasSymbol); const typeArgumentNodes = mapToTypeNodeArray(type.aliasTypeArguments); @@ -2352,18 +2340,14 @@ namespace ts { return createAnonymousTypeNode(type); } - // TODO: implement when this is testable. - // else if (type.flags & TypeFlags.StringOrNumberLiteral) { - // writer.writeStringLiteral(literalTypeToString(type)); + // TODO (aozgaa): implement string and number literals here once there is a testable case. if (type.flags & TypeFlags.Index) { - // TODO: test. - const indexType = getIndexType(getApparentType((type).type)); - const indexTypeNode = createTypeNodeWorker(indexType); + const indexedType = (type).type; + const indexTypeNode = createTypeNodeWorker(indexedType); return createTypeOperatorNode(indexTypeNode); } if (type.flags & TypeFlags.IndexedAccess) { - // TODO: test. const objectTypeNode = createTypeNodeWorker((type).objectType); const indexTypeNode = createTypeNodeWorker((type).indexType); return createIndexedAccessTypeNode(objectTypeNode, indexTypeNode); @@ -2375,27 +2359,15 @@ namespace ts { return types && asNodeArray(types.map(createTypeNodeWorker) as TypeNode[]); } - function createNameFromSymbol(symbol: Symbol): Identifier; - function createNameFromSymbol(symbol: Symbol): EntityName; - function createNameFromSymbol(symbol: Symbol): EntityName { - symbol; enclosingDeclaration; - // TODO: actually implement this - return createIdentifier(symbolToString(symbol, enclosingDeclaration)); - } - function createMappedTypeNodeFromType(type: MappedType) { Debug.assert(!!(type.flags & TypeFlags.Object)); - - // TODO: does typeParameter have the same constraint or do we need to overwrite it somehow? const typeParameter = getTypeParameterFromMappedType(type); - // const constraintType = getConstraintTypeFromMappedType(type); const typeParameterNode = createTypeParameterDeclarationFromType(typeParameter, enclosingDeclaration); const templateTypeNode = createTypeNode(getTemplateTypeFromMappedType(type), enclosingDeclaration); const readonlyToken = (type).declaration && (type).declaration.readonlyToken ? createToken(SyntaxKind.ReadonlyKeyword) : undefined; const questionToken = (type).declaration && (type).declaration.questionToken ? createToken(SyntaxKind.QuestionToken) : undefined; - // TODO: test. return createMappedTypeNode(readonlyToken, typeParameterNode, questionToken, templateTypeNode); } @@ -2501,7 +2473,6 @@ namespace ts { return createTupleTypeNode(typeArguments.length > 0 ? mapToTypeNodeArray(typeArguments.slice(0, getTypeReferenceArity(type))) : undefined); } else { - // TODO: handle type parameters in qualified names... const outerTypeParameters = type.target.outerTypeParameters; let i = 0; let qualifiedName: QualifiedName | undefined = undefined; @@ -2516,10 +2487,9 @@ namespace ts { } while (i < length && getParentSymbolOfTypeParameter(outerTypeParameters[i]) === parent); // When type parameters are their own type arguments for the whole group (i.e. we have // the default outer type arguments), we don't show the group. - // TODO: figure out how to handle type arguments if (!rangeEquals(outerTypeParameters, typeArguments, start, i)) { const name = createNameFromSymbol(parent); - const qualifiedNamePart = name; // createTypeReferenceNode(name, mapToTypeNodeArray(typeArguments.slice(start, i - start))); + const qualifiedNamePart = name; if (!qualifiedName) { qualifiedName = createQualifiedName(qualifiedNamePart, /*right*/undefined); } @@ -2534,7 +2504,6 @@ namespace ts { let entityName: EntityName = undefined; const nameIdentifier = createNameFromSymbol(type.symbol); if (qualifiedName) { - // TODO: handle checking of type arguments for qualified names? Debug.assert(!qualifiedName.right); qualifiedName.right = nameIdentifier; entityName = qualifiedName; @@ -2597,6 +2566,68 @@ namespace ts { } return typeElements.length ? typeElements : undefined; } + + function createNameFromSymbol(symbol: Symbol): Identifier; + function createNameFromSymbol(symbol: Symbol): EntityName; + function createNameFromSymbol(symbol: Symbol): EntityName { + let parentSymbol: Symbol; + symbol; enclosingDeclaration; + let meaning: SymbolFlags; + + // Get qualified name if the symbol is not a type parameter + // and there is an enclosing declaration. + let chain: Symbol[]; + const isTypeParameter = symbol.flags & SymbolFlags.TypeParameter; + if (!isTypeParameter && enclosingDeclaration) { + chain = getSymbolChain(symbol, meaning, /*endOfChain*/ true); + Debug.assert(chain && chain.length > 0); + } + else { + chain = [symbol]; + } + + const result = createEntityNameFromSymbolChain(chain, chain.length - 1); + return result; + + function createEntityNameFromSymbolChain(chain: Symbol[], index: number): EntityName { + Debug.assert(chain && 0 <= index && index < chain.length); + const identifier = createIdentifier(getNameOfSymbol(chain[index])); + return index > 0 ? createQualifiedName(createEntityNameFromSymbolChain(chain, index - 1), identifier) : identifier; + } + + /** @param endOfChain Set to false for recursive calls; non-recursive calls should always output something. */ + function getSymbolChain(symbol: Symbol, meaning: SymbolFlags, endOfChain: boolean): Symbol[] | undefined { + let accessibleSymbolChain = getAccessibleSymbolChain(symbol, enclosingDeclaration, meaning, /*useOnlyExternalAliasing*/false); + + if (!accessibleSymbolChain || + needsQualification(accessibleSymbolChain[0], enclosingDeclaration, accessibleSymbolChain.length === 1 ? meaning : getQualifiedLeftMeaning(meaning))) { + + // Go up and add our parent. + const parent = getParentOfSymbol(accessibleSymbolChain ? accessibleSymbolChain[0] : symbol); + if (parent) { + const parentChain = getSymbolChain(parent, getQualifiedLeftMeaning(meaning), /*endOfChain*/ false); + if (parentChain) { + accessibleSymbolChain = parentChain.concat(accessibleSymbolChain || [symbol]); + } + } + } + + if (accessibleSymbolChain) { + return accessibleSymbolChain; + } + + else if ( + // If this is the last part of outputting the symbol, always output. The cases apply only to parent symbols. + endOfChain || + // If a parent symbol is an external module, don't write it. (We prefer just `x` vs `"foo/bar".x`.) + !(!parentSymbol && ts.forEach(symbol.declarations, hasExternalModuleSymbol)) && + // If a parent symbol is an anonymous type, don't write it. + !(symbol.flags & (SymbolFlags.TypeLiteral | SymbolFlags.ObjectLiteral))) { + + return [symbol]; + } + } + } } } diff --git a/src/compiler/visitor.ts b/src/compiler/visitor.ts index 5d63126ad0a..1410c7aaabd 100644 --- a/src/compiler/visitor.ts +++ b/src/compiler/visitor.ts @@ -333,13 +333,18 @@ namespace ts { return updateTypeOperatorNode(node, visitNode((node).type, visitor, isTypeNode)); case SyntaxKind.IndexedAccessType: return updateIndexedAccessTypeNode((node) - , visitNode((node).objectType, visitor, isTypeNode) - , visitNode((node).indexType, visitor, isTypeNode)); + , visitNode((node).objectType, visitor, isTypeNode) + , visitNode((node).indexType, visitor, isTypeNode)); case SyntaxKind.MappedType: - throw new Error("reached unsupported type in visitor."); + return updateMappedTypeNode((node) + , visitNode((node).readonlyToken, visitor, isToken) + , visitNode((node).typeParameter, visitor, isTypeParameter) + , visitNode((node).questionToken, visitor, isToken) + , visitNode((node).type, visitor, isTypeNode)); + case SyntaxKind.LiteralType: return updateLiteralTypeNode(node - , visitNode((node).literal, visitor, isExpression)); + , visitNode((node).literal, visitor, isExpression)); // Type Declarations diff --git a/src/services/codefixes/fixAddMissingMember.ts b/src/services/codefixes/fixAddMissingMember.ts index ab973bb5772..53990927575 100644 --- a/src/services/codefixes/fixAddMissingMember.ts +++ b/src/services/codefixes/fixAddMissingMember.ts @@ -50,7 +50,6 @@ namespace ts.codefix { , /*questionToken*/ undefined , typeNode , /*initializer*/ undefined); - // TODO: make index signature. const propertyChangeTracker = textChanges.ChangeTracker.fromCodeFixContext(context); propertyChangeTracker.insertNodeAfter(sourceFile, openBrace, property, { suffix: context.newLineCharacter }); diff --git a/src/services/codefixes/helpers.ts b/src/services/codefixes/helpers.ts index 8693cc453bb..e642da2159a 100644 --- a/src/services/codefixes/helpers.ts +++ b/src/services/codefixes/helpers.ts @@ -4,7 +4,6 @@ namespace ts.codefix { export function newNodesToChanges(newNodes: Node[], insertAfter: Node, context: CodeFixContext) { const sourceFile = context.sourceFile; if (!(newNodes)) { - // TODO: make the appropriate value flow through gracefully. throw new Error("newNodesToChanges expects an array"); } @@ -13,6 +12,7 @@ namespace ts.codefix { for (const newNode of newNodes) { changeTracker.insertNodeAfter(sourceFile, insertAfter, newNode, { suffix: context.newLineCharacter }); } + // TODO (aozgaa): concatenate changes into a single change. return changeTracker.getChanges(); } @@ -51,7 +51,6 @@ namespace ts.codefix { } const declaration = declarations[0] as Declaration; - // TODO: get name as identifier or computer property name, etc. const name = declaration.name ? getSynthesizedDeepClone(declaration.name) as PropertyName : undefined; const visibilityModifier = createVisibilityModifier(getModifierFlags(declaration)); const modifiers = visibilityModifier ? [visibilityModifier] : undefined; @@ -63,7 +62,6 @@ namespace ts.codefix { case SyntaxKind.PropertySignature: case SyntaxKind.PropertyDeclaration: const typeNode = checker.createTypeNode(type, enclosingDeclaration); - // TODO: add modifiers. const property = createProperty( /*decorators*/undefined , modifiers @@ -96,7 +94,6 @@ namespace ts.codefix { let signatureDeclarations = []; for (let i = 0; i < signatures.length; i++) { - // TODO: make signatures instead of methods const signature = signatures[i]; const signatureParts = checker.createSignatureParts(signature, enclosingDeclaration); signatureDeclarations.push(createMethod( diff --git a/tests/cases/fourslash/codeFixClassImplementInterfaceIndexType.ts b/tests/cases/fourslash/codeFixClassImplementInterfaceIndexType.ts new file mode 100644 index 00000000000..ed5ea06ebb6 --- /dev/null +++ b/tests/cases/fourslash/codeFixClassImplementInterfaceIndexType.ts @@ -0,0 +1,10 @@ +/// + +//// interface I { +//// x: keyof X; +//// } +//// class C implements I {[| |]} + +verify.rangeAfterCodeFix(` +x: keyof Y; +`); diff --git a/tests/cases/fourslash/codeFixClassImplementInterfaceMappedType.ts b/tests/cases/fourslash/codeFixClassImplementInterfaceMappedType.ts new file mode 100644 index 00000000000..64676bce860 --- /dev/null +++ b/tests/cases/fourslash/codeFixClassImplementInterfaceMappedType.ts @@ -0,0 +1,10 @@ +/// + +//// interface I { +//// x: { readonly [K in keyof X]: X[K] }; +//// } +//// class C implements I {[| |]} + +verify.rangeAfterCodeFix(` +x: { readonly [K in keyof X]: Y[K]; }; +`);