|
export function typeToValue( |
|
typeNode: ts.TypeNode|null, checker: ts.TypeChecker): TypeValueReference { |
|
// It's not possible to get a value expression if the parameter doesn't even have a type. |
|
if (typeNode === null) { |
|
return missingType(); |
|
} |
|
|
|
if (!ts.isTypeReferenceNode(typeNode)) { |
|
return unsupportedType(typeNode); |
|
} |
|
|
|
const symbols = resolveTypeSymbols(typeNode, checker); |
|
if (symbols === null) { |
|
return unknownReference(typeNode); |
|
} |
|
|
|
const {local, decl} = symbols; |
|
// It's only valid to convert a type reference to a value reference if the type actually |
|
// has a value declaration associated with it. Note that const enums are an exception, |
|
// because while they do have a value declaration, they don't exist at runtime. |
|
if (decl.valueDeclaration === undefined || decl.flags & ts.SymbolFlags.ConstEnum) { |
|
let typeOnlyDecl: ts.Declaration|null = null; |
|
if (decl.declarations !== undefined && decl.declarations.length > 0) { |
|
typeOnlyDecl = decl.declarations[0]; |
|
} |
|
return noValueDeclaration(typeNode, typeOnlyDecl); |
|
} |
|
|
|
// The type points to a valid value declaration. Rewrite the TypeReference into an |
|
// Expression which references the value pointed to by the TypeReference, if possible. |
|
|
|
// Look at the local `ts.Symbol`'s declarations and see if it comes from an import |
|
// statement. If so, extract the module specifier and the name of the imported type. |
|
const firstDecl = local.declarations && local.declarations[0]; |
|
if (firstDecl !== undefined) { |
|
if (ts.isImportClause(firstDecl) && firstDecl.name !== undefined) { |
|
// This is a default import. |
|
// import Foo from 'foo'; |
|
|
|
if (firstDecl.isTypeOnly) { |
|
// Type-only imports cannot be represented as value. |
|
return typeOnlyImport(typeNode, firstDecl); |
|
} |
|
|
|
return { |
|
kind: TypeValueReferenceKind.LOCAL, |
|
expression: firstDecl.name, |
|
defaultImportStatement: firstDecl.parent, |
|
}; |
|
} else if (ts.isImportSpecifier(firstDecl)) { |
|
// The symbol was imported by name |
|
// import {Foo} from 'foo'; |
|
// or |
|
// import {Foo as Bar} from 'foo'; |
|
|
|
if (firstDecl.parent.parent.isTypeOnly) { |
|
// Type-only imports cannot be represented as value. |
|
return typeOnlyImport(typeNode, firstDecl.parent.parent); |
|
} |
|
|
|
// Determine the name to import (`Foo`) from the import specifier, as the symbol names of |
|
// the imported type could refer to a local alias (like `Bar` in the example above). |
|
const importedName = (firstDecl.propertyName || firstDecl.name).text; |
|
|
|
// The first symbol name refers to the local name, which is replaced by `importedName` above. |
|
// Any remaining symbol names make up the complete path to the value. |
|
const [_localName, ...nestedPath] = symbols.symbolNames; |
|
|
|
const moduleName = extractModuleName(firstDecl.parent.parent.parent); |
|
return { |
|
kind: TypeValueReferenceKind.IMPORTED, |
|
valueDeclaration: decl.valueDeclaration, |
|
moduleName, |
|
importedName, |
|
nestedPath |
|
}; |
|
} else if (ts.isNamespaceImport(firstDecl)) { |
|
// The import is a namespace import |
|
// import * as Foo from 'foo'; |
|
|
|
if (firstDecl.parent.isTypeOnly) { |
|
// Type-only imports cannot be represented as value. |
|
return typeOnlyImport(typeNode, firstDecl.parent); |
|
} |
|
|
|
if (symbols.symbolNames.length === 1) { |
|
// The type refers to the namespace itself, which cannot be represented as a value. |
|
return namespaceImport(typeNode, firstDecl.parent); |
|
} |
|
|
|
// The first symbol name refers to the local name of the namespace, which is is discarded |
|
// as a new namespace import will be generated. This is followed by the symbol name that needs |
|
// to be imported and any remaining names that constitute the complete path to the value. |
|
const [_ns, importedName, ...nestedPath] = symbols.symbolNames; |
|
|
|
const moduleName = extractModuleName(firstDecl.parent.parent); |
|
return { |
|
kind: TypeValueReferenceKind.IMPORTED, |
|
valueDeclaration: decl.valueDeclaration, |
|
moduleName, |
|
importedName, |
|
nestedPath |
|
}; |
|
} |
|
} |
|
|
|
// If the type is not imported, the type reference can be converted into an expression as is. |
|
const expression = typeNodeToValueExpr(typeNode); |
|
if (expression !== null) { |
|
return { |
|
kind: TypeValueReferenceKind.LOCAL, |
|
expression, |
|
defaultImportStatement: null, |
|
}; |
|
} else { |
|
return unsupportedType(typeNode); |
|
} |
|
} |
For upcoming TS 4.5 our
typeToValuelogic needs to account for microsoft/TypeScript#45998, where we should report an error if an import specifier is used that has a type-only modifier for a DI token.angular/packages/compiler-cli/src/ngtsc/reflection/src/type_to_value.ts
Lines 20 to 137 in a32a317
For example the following should be invalid:
The compiler is already capable of reporting errors for type-only import statements, it just needs to be expanded for type-only import specifiers.