`,
// the `dir` will be skipped, but it's needed in language service.
const firstChild = host.children[0];
if (firstChild instanceof compiler.Element) {
const isMicrosyntaxTemplate = host instanceof compiler.Template && sourceSpanEqual(firstChild.sourceSpan, host.sourceSpan);
if (isMicrosyntaxTemplate) {
const firstChildDirectives = this.typeCheckData.boundTarget.getDirectivesOfNode(firstChild);
if (firstChildDirectives !== null && directives !== null) {
directives = directives.concat(firstChildDirectives);
}
else {
directives = directives ?? firstChildDirectives;
}
}
}
if (directives === null) {
return null;
}
const directive = directives.find((m) => m.ref.node === directiveDeclaration);
if (directive) {
return directive;
}
const originalFile = directiveDeclaration.getSourceFile()[NgOriginalFile];
if (originalFile !== undefined) {
// This is a preliminary check ahead of a more expensive search
const hasPotentialCandidate = directives.find((m) => m.ref.node.name.text === directiveDeclaration.name?.text);
if (hasPotentialCandidate) {
// In case the TCB has been inlined,
// We will look for a matching class
// If we find one, we look for it in the directives array
const classWithSameName = findMatchingDirective(originalFile, directiveDeclaration);
if (classWithSameName !== null) {
return directives.find((m) => m.ref.node === classWithSameName) ?? null;
}
}
}
// Really nothing was found
return null;
}
getDirectiveModule(declaration) {
const scope = this.componentScopeReader.getScopeForComponent(declaration);
if (scope === null || scope.kind !== exports.ComponentScopeKind.NgModule) {
return null;
}
return scope.ngModule;
}
getSymbolOfBoundEvent(eventBinding) {
const consumer = this.typeCheckData.boundTarget.getConsumerOfBinding(eventBinding);
if (consumer === null) {
return null;
}
// Outputs in the TCB look like one of the two:
// * _t1["outputField"].subscribe(handler);
// * _t1.addEventListener(handler);
// Even with strict null checks disabled, we still produce the access as a separate statement
// so that it can be found here.
let expectedAccess;
if (consumer instanceof compiler.Template || consumer instanceof compiler.Element) {
expectedAccess = 'addEventListener';
}
else {
const bindingPropertyNames = consumer.outputs.getByBindingPropertyName(eventBinding.name);
if (bindingPropertyNames === null || bindingPropertyNames.length === 0) {
return null;
}
// Note that we only get the expectedAccess text from a single consumer of the binding. If
// there are multiple consumers (not supported in the `boundTarget` API) and one of them has
// an alias, it will not get matched here.
expectedAccess = bindingPropertyNames[0].classPropertyName;
}
function filter(n) {
if (!isAccessExpression(n)) {
return false;
}
if (ts.isPropertyAccessExpression(n)) {
return n.name.getText() === expectedAccess;
}
else {
return (ts.isStringLiteral(n.argumentExpression) && n.argumentExpression.text === expectedAccess);
}
}
const outputFieldAccesses = findAllMatchingNodes(this.typeCheckBlock, {
withSpan: eventBinding.keySpan,
filter,
});
const bindings = [];
for (const outputFieldAccess of outputFieldAccesses) {
if (consumer instanceof compiler.Template || consumer instanceof compiler.Element) {
if (!ts.isPropertyAccessExpression(outputFieldAccess)) {
continue;
}
const addEventListener = outputFieldAccess.name;
const tsSymbol = this.getTypeChecker().getSymbolAtLocation(addEventListener);
const tsType = this.getTypeChecker().getTypeAtLocation(addEventListener);
const positionInFile = this.getTcbPositionForNode(addEventListener);
const target = this.getSymbol(consumer);
if (target === null || tsSymbol === undefined) {
continue;
}
bindings.push({
kind: exports.SymbolKind.Binding,
tsSymbol,
tsType,
target,
tcbLocation: {
tcbPath: this.tcbPath,
isShimFile: this.tcbIsShim,
positionInFile,
},
});
}
else {
if (!ts.isElementAccessExpression(outputFieldAccess)) {
continue;
}
const tsSymbol = this.getTypeChecker().getSymbolAtLocation(outputFieldAccess.argumentExpression);
if (tsSymbol === undefined) {
continue;
}
const target = this.getDirectiveSymbolForAccessExpression(outputFieldAccess, consumer);
if (target === null) {
continue;
}
const positionInFile = this.getTcbPositionForNode(outputFieldAccess);
const tsType = this.getTypeChecker().getTypeAtLocation(outputFieldAccess);
bindings.push({
kind: exports.SymbolKind.Binding,
tsSymbol,
tsType,
target,
tcbLocation: {
tcbPath: this.tcbPath,
isShimFile: this.tcbIsShim,
positionInFile,
},
});
}
}
if (bindings.length === 0) {
return null;
}
return { kind: exports.SymbolKind.Output, bindings };
}
getSymbolOfInputBinding(binding) {
const consumer = this.typeCheckData.boundTarget.getConsumerOfBinding(binding);
if (consumer === null) {
return null;
}
if (consumer instanceof compiler.Element || consumer instanceof compiler.Template) {
const host = this.getSymbol(consumer);
return host !== null ? { kind: exports.SymbolKind.DomBinding, host } : null;
}
const nodes = findAllMatchingNodes(this.typeCheckBlock, {
withSpan: binding.sourceSpan,
filter: isAssignment,
});
const bindings = [];
for (const node of nodes) {
if (!isAccessExpression(node.left)) {
continue;
}
const signalInputAssignment = unwrapSignalInputWriteTAccessor(node.left);
let fieldAccessExpr;
let symbolInfo = null;
// Signal inputs need special treatment because they are generated with an extra keyed
// access. E.g. `_t1.prop[WriteT_ACCESSOR_SYMBOL]`. Observations:
// - The keyed access for the write type needs to be resolved for the "input type".
// - The definition symbol of the input should be the input class member, and not the
// internal write accessor. Symbol should resolve `_t1.prop`.
if (signalInputAssignment !== null) {
// Note: If the field expression for the input binding refers to just an identifier,
// then we are handling the case of a temporary variable being used for the input field.
// This is the case with `honorAccessModifiersForInputBindings = false` and in those cases
// we cannot resolve the owning directive, similar to how we guard above with `isAccessExpression`.
if (ts.isIdentifier(signalInputAssignment.fieldExpr)) {
continue;
}
const fieldSymbol = this.getSymbolOfTsNode(signalInputAssignment.fieldExpr);
const typeSymbol = this.getSymbolOfTsNode(signalInputAssignment.typeExpr);
fieldAccessExpr = signalInputAssignment.fieldExpr;
symbolInfo =
fieldSymbol === null || typeSymbol === null
? null
: {
tcbLocation: fieldSymbol.tcbLocation,
tsSymbol: fieldSymbol.tsSymbol,
tsType: typeSymbol.tsType,
};
}
else {
fieldAccessExpr = node.left;
symbolInfo = this.getSymbolOfTsNode(node.left);
}
if (symbolInfo === null || symbolInfo.tsSymbol === null) {
continue;
}
const target = this.getDirectiveSymbolForAccessExpression(fieldAccessExpr, consumer);
if (target === null) {
continue;
}
bindings.push({
...symbolInfo,
tsSymbol: symbolInfo.tsSymbol,
kind: exports.SymbolKind.Binding,
target,
});
}
if (bindings.length === 0) {
return null;
}
return { kind: exports.SymbolKind.Input, bindings };
}
getDirectiveSymbolForAccessExpression(fieldAccessExpr, { isComponent, selector, isStructural }) {
// In all cases, `_t1["index"]` or `_t1.index`, `node.expression` is _t1.
const tsSymbol = this.getTypeChecker().getSymbolAtLocation(fieldAccessExpr.expression);
if (tsSymbol?.declarations === undefined ||
tsSymbol.declarations.length === 0 ||
selector === null) {
return null;
}
const [declaration] = tsSymbol.declarations;
if (!ts.isVariableDeclaration(declaration) ||
!hasExpressionIdentifier(
// The expression identifier could be on the type (for regular directives) or the name
// (for generic directives and the ctor op).
declaration.getSourceFile(), declaration.type ?? declaration.name, ExpressionIdentifier.DIRECTIVE)) {
return null;
}
const symbol = this.getSymbolOfTsNode(declaration);
if (symbol === null ||
!isSymbolWithValueDeclaration(symbol.tsSymbol) ||
!ts.isClassDeclaration(symbol.tsSymbol.valueDeclaration)) {
return null;
}
const ref = new Reference(symbol.tsSymbol.valueDeclaration);
const ngModule = this.getDirectiveModule(symbol.tsSymbol.valueDeclaration);
return {
ref,
kind: exports.SymbolKind.Directive,
tsSymbol: symbol.tsSymbol,
tsType: symbol.tsType,
tcbLocation: symbol.tcbLocation,
isComponent,
isStructural,
selector,
ngModule,
isHostDirective: false,
isInScope: true, // TODO: this should always be in scope in this context, right?
};
}
getSymbolOfVariable(variable) {
const node = findFirstMatchingNode(this.typeCheckBlock, {
withSpan: variable.sourceSpan,
filter: ts.isVariableDeclaration,
});
if (node === null) {
return null;
}
let nodeValueSymbol = null;
if (ts.isForOfStatement(node.parent.parent)) {
nodeValueSymbol = this.getSymbolOfTsNode(node);
}
else if (node.initializer !== undefined) {
nodeValueSymbol = this.getSymbolOfTsNode(node.initializer);
}
if (nodeValueSymbol === null) {
return null;
}
return {
tsType: nodeValueSymbol.tsType,
tsSymbol: nodeValueSymbol.tsSymbol,
initializerLocation: nodeValueSymbol.tcbLocation,
kind: exports.SymbolKind.Variable,
declaration: variable,
localVarLocation: {
tcbPath: this.tcbPath,
isShimFile: this.tcbIsShim,
positionInFile: this.getTcbPositionForNode(node.name),
},
};
}
getSymbolOfReference(ref) {
const target = this.typeCheckData.boundTarget.getReferenceTarget(ref);
// Find the node for the reference declaration, i.e. `var _t2 = _t1;`
let node = findFirstMatchingNode(this.typeCheckBlock, {
withSpan: ref.sourceSpan,
filter: ts.isVariableDeclaration,
});
if (node === null || target === null || node.initializer === undefined) {
return null;
}
// Get the original declaration for the references variable, with the exception of template refs
// which are of the form var _t3 = (_t2 as any as i2.TemplateRef
)
// TODO(atscott): Consider adding an `ExpressionIdentifier` to tag variable declaration
// initializers as invalid for symbol retrieval.
const originalDeclaration = ts.isParenthesizedExpression(node.initializer) &&
ts.isAsExpression(node.initializer.expression)
? this.getTypeChecker().getSymbolAtLocation(node.name)
: this.getTypeChecker().getSymbolAtLocation(node.initializer);
if (originalDeclaration === undefined || originalDeclaration.valueDeclaration === undefined) {
return null;
}
const symbol = this.getSymbolOfTsNode(originalDeclaration.valueDeclaration);
if (symbol === null || symbol.tsSymbol === null) {
return null;
}
const referenceVarTcbLocation = {
tcbPath: this.tcbPath,
isShimFile: this.tcbIsShim,
positionInFile: this.getTcbPositionForNode(node),
};
if (target instanceof compiler.Template || target instanceof compiler.Element) {
return {
kind: exports.SymbolKind.Reference,
tsSymbol: symbol.tsSymbol,
tsType: symbol.tsType,
target,
declaration: ref,
targetLocation: symbol.tcbLocation,
referenceVarLocation: referenceVarTcbLocation,
};
}
else {
if (!ts.isClassDeclaration(target.directive.ref.node)) {
return null;
}
return {
kind: exports.SymbolKind.Reference,
tsSymbol: symbol.tsSymbol,
tsType: symbol.tsType,
declaration: ref,
target: target.directive.ref.node,
targetLocation: symbol.tcbLocation,
referenceVarLocation: referenceVarTcbLocation,
};
}
}
getSymbolOfLetDeclaration(decl) {
const node = findFirstMatchingNode(this.typeCheckBlock, {
withSpan: decl.sourceSpan,
filter: ts.isVariableDeclaration,
});
if (node === null) {
return null;
}
const nodeValueSymbol = this.getSymbolOfTsNode(node.initializer);
if (nodeValueSymbol === null) {
return null;
}
return {
tsType: nodeValueSymbol.tsType,
tsSymbol: nodeValueSymbol.tsSymbol,
initializerLocation: nodeValueSymbol.tcbLocation,
kind: exports.SymbolKind.LetDeclaration,
declaration: decl,
localVarLocation: {
tcbPath: this.tcbPath,
isShimFile: this.tcbIsShim,
positionInFile: this.getTcbPositionForNode(node.name),
},
};
}
getSymbolOfPipe(expression) {
const methodAccess = findFirstMatchingNode(this.typeCheckBlock, {
withSpan: expression.nameSpan,
filter: ts.isPropertyAccessExpression,
});
if (methodAccess === null) {
return null;
}
const pipeVariableNode = methodAccess.expression;
const pipeDeclaration = this.getTypeChecker().getSymbolAtLocation(pipeVariableNode);
if (pipeDeclaration === undefined || pipeDeclaration.valueDeclaration === undefined) {
return null;
}
const pipeInstance = this.getSymbolOfTsNode(pipeDeclaration.valueDeclaration);
// The instance should never be null, nor should the symbol lack a value declaration. This
// is because the node used to look for the `pipeInstance` symbol info is a value
// declaration of another symbol (i.e. the `pipeDeclaration` symbol).
if (pipeInstance === null || !isSymbolWithValueDeclaration(pipeInstance.tsSymbol)) {
return null;
}
const symbolInfo = this.getSymbolOfTsNode(methodAccess);
if (symbolInfo === null) {
return null;
}
return {
kind: exports.SymbolKind.Pipe,
...symbolInfo,
classSymbol: {
...pipeInstance,
tsSymbol: pipeInstance.tsSymbol,
},
};
}
getSymbolOfTemplateExpression(expression) {
if (expression instanceof compiler.ASTWithSource) {
expression = expression.ast;
}
const expressionTarget = this.typeCheckData.boundTarget.getExpressionTarget(expression);
if (expressionTarget !== null) {
return this.getSymbol(expressionTarget);
}
let withSpan = expression.sourceSpan;
// The `name` part of a `PropertyWrite` and `ASTWithName` do not have their own
// AST so there is no way to retrieve a `Symbol` for just the `name` via a specific node.
// Also skipping SafePropertyReads as it breaks nullish coalescing not nullable extended diagnostic
if (expression instanceof compiler.PropertyWrite ||
(expression instanceof compiler.ASTWithName && !(expression instanceof compiler.SafePropertyRead))) {
withSpan = expression.nameSpan;
}
let node = null;
// Property reads in templates usually map to a `PropertyAccessExpression`
// (e.g. `ctx.foo`) so try looking for one first.
if (expression instanceof compiler.PropertyRead) {
node = findFirstMatchingNode(this.typeCheckBlock, {
withSpan,
filter: ts.isPropertyAccessExpression,
});
}
// Otherwise fall back to searching for any AST node.
if (node === null) {
node = findFirstMatchingNode(this.typeCheckBlock, { withSpan, filter: anyNodeFilter });
}
if (node === null) {
return null;
}
while (ts.isParenthesizedExpression(node)) {
node = node.expression;
}
// - If we have safe property read ("a?.b") we want to get the Symbol for b, the `whenTrue`
// expression.
// - If our expression is a pipe binding ("a | test:b:c"), we want the Symbol for the
// `transform` on the pipe.
// - Otherwise, we retrieve the symbol for the node itself with no special considerations
if (expression instanceof compiler.SafePropertyRead && ts.isConditionalExpression(node)) {
const whenTrueSymbol = this.getSymbolOfTsNode(node.whenTrue);
if (whenTrueSymbol === null) {
return null;
}
return {
...whenTrueSymbol,
kind: exports.SymbolKind.Expression,
// Rather than using the type of only the `whenTrue` part of the expression, we should
// still get the type of the whole conditional expression to include `|undefined`.
tsType: this.getTypeChecker().getTypeAtLocation(node),
};
}
else {
const symbolInfo = this.getSymbolOfTsNode(node);
return symbolInfo === null ? null : { ...symbolInfo, kind: exports.SymbolKind.Expression };
}
}
getSymbolOfTsNode(node) {
while (ts.isParenthesizedExpression(node)) {
node = node.expression;
}
let tsSymbol;
if (ts.isPropertyAccessExpression(node)) {
tsSymbol = this.getTypeChecker().getSymbolAtLocation(node.name);
}
else if (ts.isCallExpression(node)) {
tsSymbol = this.getTypeChecker().getSymbolAtLocation(node.expression);
}
else {
tsSymbol = this.getTypeChecker().getSymbolAtLocation(node);
}
const positionInFile = this.getTcbPositionForNode(node);
const type = this.getTypeChecker().getTypeAtLocation(node);
return {
// If we could not find a symbol, fall back to the symbol on the type for the node.
// Some nodes won't have a "symbol at location" but will have a symbol for the type.
// Examples of this would be literals and `document.createElement('div')`.
tsSymbol: tsSymbol ?? type.symbol ?? null,
tsType: type,
tcbLocation: {
tcbPath: this.tcbPath,
isShimFile: this.tcbIsShim,
positionInFile,
},
};
}
getTcbPositionForNode(node) {
if (ts.isTypeReferenceNode(node)) {
return this.getTcbPositionForNode(node.typeName);
}
else if (ts.isQualifiedName(node)) {
return node.right.getStart();
}
else if (ts.isPropertyAccessExpression(node)) {
return node.name.getStart();
}
else if (ts.isElementAccessExpression(node)) {
return node.argumentExpression.getStart();
}
else {
return node.getStart();
}
}
}
/** Filter predicate function that matches any AST node. */
function anyNodeFilter(n) {
return true;
}
function sourceSpanEqual(a, b) {
return a.start.offset === b.start.offset && a.end.offset === b.end.offset;
}
function unwrapSignalInputWriteTAccessor(expr) {
// e.g. `_t2.inputA[i2.ɵINPUT_SIGNAL_BRAND_WRITE_TYPE]`
// 1. Assert that we are dealing with an element access expression.
// 2. Assert that we are dealing with a signal brand symbol access in the argument expression.
if (!ts.isElementAccessExpression(expr) ||
!ts.isPropertyAccessExpression(expr.argumentExpression)) {
return null;
}
// Assert that the property access in the element access is a simple identifier and
// refers to `ɵINPUT_SIGNAL_BRAND_WRITE_TYPE`.
if (!ts.isIdentifier(expr.argumentExpression.name) ||
expr.argumentExpression.name.text !== compiler.Identifiers.InputSignalBrandWriteType.name) {
return null;
}
// Assert that the expression is either:
// - `_t2.inputA[ɵINPUT_SIGNAL_BRAND_WRITE_TYPE]` or (common case)
// - or `_t2['input-A'][ɵINPUT_SIGNAL_BRAND_WRITE_TYPE]` (non-identifier input field names)
// - or `_dirInput[ɵINPUT_SIGNAL_BRAND_WRITE_TYPE` (honorAccessModifiersForInputBindings=false)
// This is checked for type safety and to catch unexpected cases.
if (!ts.isPropertyAccessExpression(expr.expression) &&
!ts.isElementAccessExpression(expr.expression) &&
!ts.isIdentifier(expr.expression)) {
throw new Error('Unexpected expression for signal input write type.');
}
return {
fieldExpr: expr.expression,
typeExpr: expr,
};
}
/**
* Looks for a class declaration in the original source file that matches a given directive
* from the type check source file.
*
* @param originalSourceFile The original source where the runtime code resides
* @param directiveDeclarationInTypeCheckSourceFile The directive from the type check source file
*/
function findMatchingDirective(originalSourceFile, directiveDeclarationInTypeCheckSourceFile) {
const className = directiveDeclarationInTypeCheckSourceFile.name?.text ?? '';
// We build an index of the class declarations with the same name
// To then compare the indexes to confirm we found the right class declaration
const ogClasses = collectClassesWithName(originalSourceFile, className);
const typecheckClasses = collectClassesWithName(directiveDeclarationInTypeCheckSourceFile.getSourceFile(), className);
return ogClasses[typecheckClasses.indexOf(directiveDeclarationInTypeCheckSourceFile)] ?? null;
}
/**
* Builds a list of class declarations of a given name
* Is used as a index based reference to compare class declarations
* between the typecheck source file and the original source file
*/
function collectClassesWithName(sourceFile, className) {
const classes = [];
function visit(node) {
if (ts.isClassDeclaration(node) && node.name?.text === className) {
classes.push(node);
}
ts.forEachChild(node, visit);
}
sourceFile.forEachChild(visit);
return classes;
}
const REGISTRY = new compiler.DomElementSchemaRegistry();
/**
* Primary template type-checking engine, which performs type-checking using a
* `TypeCheckingProgramStrategy` for type-checking program maintenance, and the
* `ProgramTypeCheckAdapter` for generation of template type-checking code.
*/
class TemplateTypeCheckerImpl {
originalProgram;
programDriver;
typeCheckAdapter;
config;
refEmitter;
reflector;
compilerHost;
priorBuild;
metaReader;
localMetaReader;
ngModuleIndex;
componentScopeReader;
typeCheckScopeRegistry;
perf;
state = new Map();
/**
* Stores the `CompletionEngine` which powers autocompletion for each component class.
*
* Must be invalidated whenever the component's template or the `ts.Program` changes. Invalidation
* on template changes is performed within this `TemplateTypeCheckerImpl` instance. When the
* `ts.Program` changes, the `TemplateTypeCheckerImpl` as a whole is destroyed and replaced.
*/
completionCache = new Map();
/**
* Stores the `SymbolBuilder` which creates symbols for each component class.
*
* Must be invalidated whenever the component's template or the `ts.Program` changes. Invalidation
* on template changes is performed within this `TemplateTypeCheckerImpl` instance. When the
* `ts.Program` changes, the `TemplateTypeCheckerImpl` as a whole is destroyed and replaced.
*/
symbolBuilderCache = new Map();
/**
* Stores directives and pipes that are in scope for each component.
*
* Unlike other caches, the scope of a component is not affected by its template. It will be
* destroyed when the `ts.Program` changes and the `TemplateTypeCheckerImpl` as a whole is
* destroyed and replaced.
*/
scopeCache = new Map();
/**
* Stores potential element tags for each component (a union of DOM tags as well as directive
* tags).
*
* Unlike other caches, the scope of a component is not affected by its template. It will be
* destroyed when the `ts.Program` changes and the `TemplateTypeCheckerImpl` as a whole is
* destroyed and replaced.
*/
elementTagCache = new Map();
isComplete = false;
priorResultsAdopted = false;
constructor(originalProgram, programDriver, typeCheckAdapter, config, refEmitter, reflector, compilerHost, priorBuild, metaReader, localMetaReader, ngModuleIndex, componentScopeReader, typeCheckScopeRegistry, perf) {
this.originalProgram = originalProgram;
this.programDriver = programDriver;
this.typeCheckAdapter = typeCheckAdapter;
this.config = config;
this.refEmitter = refEmitter;
this.reflector = reflector;
this.compilerHost = compilerHost;
this.priorBuild = priorBuild;
this.metaReader = metaReader;
this.localMetaReader = localMetaReader;
this.ngModuleIndex = ngModuleIndex;
this.componentScopeReader = componentScopeReader;
this.typeCheckScopeRegistry = typeCheckScopeRegistry;
this.perf = perf;
}
getTemplate(component, optimizeFor) {
const { data } = this.getLatestComponentState(component, optimizeFor);
return data?.template ?? null;
}
getHostElement(directive, optimizeFor) {
const { data } = this.getLatestComponentState(directive, optimizeFor);
return data?.hostElement ?? null;
}
getUsedDirectives(component) {
return this.getLatestComponentState(component).data?.boundTarget.getUsedDirectives() ?? null;
}
getUsedPipes(component) {
return this.getLatestComponentState(component).data?.boundTarget.getUsedPipes() ?? null;
}
getLatestComponentState(component, optimizeFor = exports.OptimizeFor.SingleFile) {
switch (optimizeFor) {
case exports.OptimizeFor.WholeProgram:
this.ensureAllShimsForAllFiles();
break;
case exports.OptimizeFor.SingleFile:
this.ensureShimForComponent(component);
break;
}
const sf = component.getSourceFile();
const sfPath = absoluteFromSourceFile(sf);
const shimPath = TypeCheckShimGenerator.shimFor(sfPath);
const fileRecord = this.getFileData(sfPath);
if (!fileRecord.shimData.has(shimPath)) {
return { data: null, tcb: null, tcbPath: shimPath, tcbIsShim: true };
}
const id = fileRecord.sourceManager.getTypeCheckId(component);
const shimRecord = fileRecord.shimData.get(shimPath);
const program = this.programDriver.getProgram();
const shimSf = getSourceFileOrNull(program, shimPath);
if (shimSf === null || !fileRecord.shimData.has(shimPath)) {
throw new Error(`Error: no shim file in program: ${shimPath}`);
}
let tcb = findTypeCheckBlock(shimSf, id, /*isDiagnosticsRequest*/ false);
let tcbPath = shimPath;
if (tcb === null) {
// Try for an inline block.
const inlineSf = getSourceFileOrError(program, sfPath);
tcb = findTypeCheckBlock(inlineSf, id, /*isDiagnosticsRequest*/ false);
if (tcb !== null) {
tcbPath = sfPath;
}
}
let data = null;
if (shimRecord.data.has(id)) {
data = shimRecord.data.get(id);
}
return { data, tcb, tcbPath, tcbIsShim: tcbPath === shimPath };
}
isTrackedTypeCheckFile(filePath) {
return this.getFileAndShimRecordsForPath(filePath) !== null;
}
getFileRecordForTcbLocation({ tcbPath, isShimFile, }) {
if (!isShimFile) {
// The location is not within a shim file but corresponds with an inline TCB in an original
// source file; we can obtain the record directly by its path.
if (this.state.has(tcbPath)) {
return this.state.get(tcbPath);
}
else {
return null;
}
}
// The location is within a type-checking shim file; find the type-checking data that owns this
// shim path.
const records = this.getFileAndShimRecordsForPath(tcbPath);
if (records !== null) {
return records.fileRecord;
}
else {
return null;
}
}
getFileAndShimRecordsForPath(shimPath) {
for (const fileRecord of this.state.values()) {
if (fileRecord.shimData.has(shimPath)) {
return { fileRecord, shimRecord: fileRecord.shimData.get(shimPath) };
}
}
return null;
}
getSourceMappingAtTcbLocation(tcbLocation) {
const fileRecord = this.getFileRecordForTcbLocation(tcbLocation);
if (fileRecord === null) {
return null;
}
const shimSf = this.programDriver.getProgram().getSourceFile(tcbLocation.tcbPath);
if (shimSf === undefined) {
return null;
}
return getSourceMapping(shimSf, tcbLocation.positionInFile, fileRecord.sourceManager,
/*isDiagnosticsRequest*/ false);
}
generateAllTypeCheckBlocks() {
this.ensureAllShimsForAllFiles();
}
/**
* Retrieve type-checking and template parse diagnostics from the given `ts.SourceFile` using the
* most recent type-checking program.
*/
getDiagnosticsForFile(sf, optimizeFor) {
switch (optimizeFor) {
case exports.OptimizeFor.WholeProgram:
this.ensureAllShimsForAllFiles();
break;
case exports.OptimizeFor.SingleFile:
this.ensureAllShimsForOneFile(sf);
break;
}
return this.perf.inPhase(exports.PerfPhase.TtcDiagnostics, () => {
const sfPath = absoluteFromSourceFile(sf);
const fileRecord = this.state.get(sfPath);
const typeCheckProgram = this.programDriver.getProgram();
const diagnostics = [];
if (fileRecord.hasInlines) {
const inlineSf = getSourceFileOrError(typeCheckProgram, sfPath);
diagnostics.push(...typeCheckProgram
.getSemanticDiagnostics(inlineSf)
.map((diag) => convertDiagnostic(diag, fileRecord.sourceManager)));
}
for (const [shimPath, shimRecord] of fileRecord.shimData) {
const shimSf = getSourceFileOrError(typeCheckProgram, shimPath);
diagnostics.push(...typeCheckProgram
.getSemanticDiagnostics(shimSf)
.map((diag) => convertDiagnostic(diag, fileRecord.sourceManager)));
diagnostics.push(...shimRecord.genesisDiagnostics);
for (const templateData of shimRecord.data.values()) {
diagnostics.push(...templateData.templateParsingDiagnostics);
}
}
return diagnostics.filter((diag) => diag !== null);
});
}
getDiagnosticsForComponent(component) {
this.ensureShimForComponent(component);
return this.perf.inPhase(exports.PerfPhase.TtcDiagnostics, () => {
const sf = component.getSourceFile();
const sfPath = absoluteFromSourceFile(sf);
const shimPath = TypeCheckShimGenerator.shimFor(sfPath);
const fileRecord = this.getFileData(sfPath);
if (!fileRecord.shimData.has(shimPath)) {
return [];
}
const id = fileRecord.sourceManager.getTypeCheckId(component);
const shimRecord = fileRecord.shimData.get(shimPath);
const typeCheckProgram = this.programDriver.getProgram();
const diagnostics = [];
if (shimRecord.hasInlines) {
const inlineSf = getSourceFileOrError(typeCheckProgram, sfPath);
diagnostics.push(...typeCheckProgram
.getSemanticDiagnostics(inlineSf)
.map((diag) => convertDiagnostic(diag, fileRecord.sourceManager)));
}
const shimSf = getSourceFileOrError(typeCheckProgram, shimPath);
diagnostics.push(...typeCheckProgram
.getSemanticDiagnostics(shimSf)
.map((diag) => convertDiagnostic(diag, fileRecord.sourceManager)));
diagnostics.push(...shimRecord.genesisDiagnostics);
for (const templateData of shimRecord.data.values()) {
diagnostics.push(...templateData.templateParsingDiagnostics);
}
return diagnostics.filter((diag) => diag !== null && diag.typeCheckId === id);
});
}
getTypeCheckBlock(component) {
return this.getLatestComponentState(component).tcb;
}
getGlobalCompletions(context, component, node) {
const engine = this.getOrCreateCompletionEngine(component);
if (engine === null) {
return null;
}
return this.perf.inPhase(exports.PerfPhase.TtcAutocompletion, () => engine.getGlobalCompletions(context, node));
}
getExpressionCompletionLocation(ast, component) {
const engine = this.getOrCreateCompletionEngine(component);
if (engine === null) {
return null;
}
return this.perf.inPhase(exports.PerfPhase.TtcAutocompletion, () => engine.getExpressionCompletionLocation(ast));
}
getLiteralCompletionLocation(node, component) {
const engine = this.getOrCreateCompletionEngine(component);
if (engine === null) {
return null;
}
return this.perf.inPhase(exports.PerfPhase.TtcAutocompletion, () => engine.getLiteralCompletionLocation(node));
}
invalidateClass(clazz) {
this.completionCache.delete(clazz);
this.symbolBuilderCache.delete(clazz);
this.scopeCache.delete(clazz);
this.elementTagCache.delete(clazz);
const sf = clazz.getSourceFile();
const sfPath = absoluteFromSourceFile(sf);
const shimPath = TypeCheckShimGenerator.shimFor(sfPath);
const fileData = this.getFileData(sfPath);
fileData.sourceManager.getTypeCheckId(clazz);
fileData.shimData.delete(shimPath);
fileData.isComplete = false;
this.isComplete = false;
}
getExpressionTarget(expression, clazz) {
return (this.getLatestComponentState(clazz).data?.boundTarget.getExpressionTarget(expression) ?? null);
}
makeTemplateDiagnostic(clazz, sourceSpan, category, errorCode, message, relatedInformation) {
const sfPath = absoluteFromSourceFile(clazz.getSourceFile());
const fileRecord = this.state.get(sfPath);
const id = fileRecord.sourceManager.getTypeCheckId(clazz);
const mapping = fileRecord.sourceManager.getTemplateSourceMapping(id);
return {
...makeTemplateDiagnostic(id, mapping, sourceSpan, category, ngErrorCode(errorCode), message, relatedInformation),
__ngCode: errorCode,
};
}
getOrCreateCompletionEngine(component) {
if (this.completionCache.has(component)) {
return this.completionCache.get(component);
}
const { tcb, data, tcbPath, tcbIsShim } = this.getLatestComponentState(component);
if (tcb === null || data === null) {
return null;
}
const engine = new CompletionEngine(tcb, data, tcbPath, tcbIsShim);
this.completionCache.set(component, engine);
return engine;
}
maybeAdoptPriorResults() {
if (this.priorResultsAdopted) {
return;
}
for (const sf of this.originalProgram.getSourceFiles()) {
if (sf.isDeclarationFile || isShim(sf)) {
continue;
}
const sfPath = absoluteFromSourceFile(sf);
if (this.state.has(sfPath)) {
const existingResults = this.state.get(sfPath);
if (existingResults.isComplete) {
// All data for this file has already been generated, so no need to adopt anything.
continue;
}
}
const previousResults = this.priorBuild.priorTypeCheckingResultsFor(sf);
if (previousResults === null || !previousResults.isComplete) {
continue;
}
this.perf.eventCount(exports.PerfEvent.ReuseTypeCheckFile);
this.state.set(sfPath, previousResults);
}
this.priorResultsAdopted = true;
}
ensureAllShimsForAllFiles() {
if (this.isComplete) {
return;
}
this.maybeAdoptPriorResults();
this.perf.inPhase(exports.PerfPhase.TcbGeneration, () => {
const host = new WholeProgramTypeCheckingHost(this);
const ctx = this.newContext(host);
for (const sf of this.originalProgram.getSourceFiles()) {
if (sf.isDeclarationFile || isShim(sf)) {
continue;
}
const sfPath = absoluteFromSourceFile(sf);
const fileData = this.getFileData(sfPath);
if (fileData.isComplete) {
continue;
}
this.typeCheckAdapter.typeCheck(sf, ctx);
fileData.isComplete = true;
}
this.updateFromContext(ctx);
this.isComplete = true;
});
}
ensureAllShimsForOneFile(sf) {
this.maybeAdoptPriorResults();
this.perf.inPhase(exports.PerfPhase.TcbGeneration, () => {
const sfPath = absoluteFromSourceFile(sf);
const fileData = this.getFileData(sfPath);
if (fileData.isComplete) {
// All data for this file is present and accounted for already.
return;
}
const host = new SingleFileTypeCheckingHost(sfPath, fileData, this);
const ctx = this.newContext(host);
this.typeCheckAdapter.typeCheck(sf, ctx);
fileData.isComplete = true;
this.updateFromContext(ctx);
});
}
ensureShimForComponent(component) {
this.maybeAdoptPriorResults();
const sf = component.getSourceFile();
const sfPath = absoluteFromSourceFile(sf);
const shimPath = TypeCheckShimGenerator.shimFor(sfPath);
const fileData = this.getFileData(sfPath);
if (fileData.shimData.has(shimPath)) {
// All data for this component is available.
return;
}
const host = new SingleShimTypeCheckingHost(sfPath, fileData, this, shimPath);
const ctx = this.newContext(host);
this.typeCheckAdapter.typeCheck(sf, ctx);
this.updateFromContext(ctx);
}
newContext(host) {
const inlining = this.programDriver.supportsInlineOperations
? InliningMode.InlineOps
: InliningMode.Error;
return new TypeCheckContextImpl(this.config, this.compilerHost, this.refEmitter, this.reflector, host, inlining, this.perf);
}
/**
* Remove any shim data that depends on inline operations applied to the type-checking program.
*
* This can be useful if new inlines need to be applied, and it's not possible to guarantee that
* they won't overwrite or corrupt existing inlines that are used by such shims.
*/
clearAllShimDataUsingInlines() {
for (const fileData of this.state.values()) {
if (!fileData.hasInlines) {
continue;
}
for (const [shimFile, shimData] of fileData.shimData.entries()) {
if (shimData.hasInlines) {
fileData.shimData.delete(shimFile);
}
}
fileData.hasInlines = false;
fileData.isComplete = false;
this.isComplete = false;
}
}
updateFromContext(ctx) {
const updates = ctx.finalize();
return this.perf.inPhase(exports.PerfPhase.TcbUpdateProgram, () => {
if (updates.size > 0) {
this.perf.eventCount(exports.PerfEvent.UpdateTypeCheckProgram);
}
this.programDriver.updateFiles(updates, exports.UpdateMode.Incremental);
this.priorBuild.recordSuccessfulTypeCheck(this.state);
this.perf.memory(exports.PerfCheckpoint.TtcUpdateProgram);
});
}
getFileData(path) {
if (!this.state.has(path)) {
this.state.set(path, {
hasInlines: false,
sourceManager: new DirectiveSourceManager(),
isComplete: false,
shimData: new Map(),
});
}
return this.state.get(path);
}
getSymbolOfNode(node, component) {
const builder = this.getOrCreateSymbolBuilder(component);
if (builder === null) {
return null;
}
return this.perf.inPhase(exports.PerfPhase.TtcSymbol, () => builder.getSymbol(node));
}
getOrCreateSymbolBuilder(component) {
if (this.symbolBuilderCache.has(component)) {
return this.symbolBuilderCache.get(component);
}
const { tcb, data, tcbPath, tcbIsShim } = this.getLatestComponentState(component);
if (tcb === null || data === null) {
return null;
}
const builder = new SymbolBuilder(tcbPath, tcbIsShim, tcb, data, this.componentScopeReader, () => this.programDriver.getProgram().getTypeChecker());
this.symbolBuilderCache.set(component, builder);
return builder;
}
getPotentialTemplateDirectives(component) {
const typeChecker = this.programDriver.getProgram().getTypeChecker();
const inScopeDirectives = this.getScopeData(component)?.directives ?? [];
const resultingDirectives = new Map();
// First, all in scope directives can be used.
for (const d of inScopeDirectives) {
resultingDirectives.set(d.ref.node, d);
}
// Any additional directives found from the global registry can be used, but are not in scope.
// In the future, we can also walk other registries for .d.ts files, or traverse the
// import/export graph.
for (const directiveClass of this.localMetaReader.getKnown(exports.MetaKind.Directive)) {
const directiveMeta = this.metaReader.getDirectiveMetadata(new Reference(directiveClass));
if (directiveMeta === null)
continue;
if (resultingDirectives.has(directiveClass))
continue;
const withScope = this.scopeDataOfDirectiveMeta(typeChecker, directiveMeta);
if (withScope === null)
continue;
resultingDirectives.set(directiveClass, { ...withScope, isInScope: false });
}
return Array.from(resultingDirectives.values());
}
getPotentialPipes(component) {
// Very similar to the above `getPotentialTemplateDirectives`, but on pipes.
const typeChecker = this.programDriver.getProgram().getTypeChecker();
const inScopePipes = this.getScopeData(component)?.pipes ?? [];
const resultingPipes = new Map();
for (const p of inScopePipes) {
resultingPipes.set(p.ref.node, p);
}
for (const pipeClass of this.localMetaReader.getKnown(exports.MetaKind.Pipe)) {
const pipeMeta = this.metaReader.getPipeMetadata(new Reference(pipeClass));
if (pipeMeta === null)
continue;
if (resultingPipes.has(pipeClass))
continue;
const withScope = this.scopeDataOfPipeMeta(typeChecker, pipeMeta);
if (withScope === null)
continue;
resultingPipes.set(pipeClass, { ...withScope, isInScope: false });
}
return Array.from(resultingPipes.values());
}
getDirectiveMetadata(dir) {
if (!isNamedClassDeclaration(dir)) {
return null;
}
return this.typeCheckScopeRegistry.getTypeCheckDirectiveMetadata(new Reference(dir));
}
getNgModuleMetadata(module) {
if (!isNamedClassDeclaration(module)) {
return null;
}
return this.metaReader.getNgModuleMetadata(new Reference(module));
}
getPipeMetadata(pipe) {
if (!isNamedClassDeclaration(pipe)) {
return null;
}
return this.metaReader.getPipeMetadata(new Reference(pipe));
}
getPotentialElementTags(component) {
if (this.elementTagCache.has(component)) {
return this.elementTagCache.get(component);
}
const tagMap = new Map();
for (const tag of REGISTRY.allKnownElementNames()) {
tagMap.set(tag, null);
}
const potentialDirectives = this.getPotentialTemplateDirectives(component);
for (const directive of potentialDirectives) {
if (directive.selector === null) {
continue;
}
for (const selector of compiler.CssSelector.parse(directive.selector)) {
if (selector.element === null || tagMap.has(selector.element)) {
// Skip this directive if it doesn't match an element tag, or if another directive has
// already been included with the same element name.
continue;
}
tagMap.set(selector.element, directive);
}
}
this.elementTagCache.set(component, tagMap);
return tagMap;
}
getPotentialDomBindings(tagName) {
const attributes = REGISTRY.allKnownAttributesOfElement(tagName);
return attributes.map((attribute) => ({
attribute,
property: REGISTRY.getMappedPropName(attribute),
}));
}
getPotentialDomEvents(tagName) {
return REGISTRY.allKnownEventsOfElement(tagName);
}
getPrimaryAngularDecorator(target) {
this.ensureAllShimsForOneFile(target.getSourceFile());
if (!isNamedClassDeclaration(target)) {
return null;
}
const ref = new Reference(target);
const dirMeta = this.metaReader.getDirectiveMetadata(ref);
if (dirMeta !== null) {
return dirMeta.decorator;
}
const pipeMeta = this.metaReader.getPipeMetadata(ref);
if (pipeMeta !== null) {
return pipeMeta.decorator;
}
const ngModuleMeta = this.metaReader.getNgModuleMetadata(ref);
if (ngModuleMeta !== null) {
return ngModuleMeta.decorator;
}
return null;
}
getOwningNgModule(component) {
if (!isNamedClassDeclaration(component)) {
return null;
}
const dirMeta = this.metaReader.getDirectiveMetadata(new Reference(component));
if (dirMeta !== null && dirMeta.isStandalone) {
return null;
}
const scope = this.componentScopeReader.getScopeForComponent(component);
if (scope === null ||
scope.kind !== exports.ComponentScopeKind.NgModule ||
!isNamedClassDeclaration(scope.ngModule)) {
return null;
}
return scope.ngModule;
}
emit(kind, refTo, inContext) {
const emittedRef = this.refEmitter.emit(refTo, inContext.getSourceFile());
if (emittedRef.kind === exports.ReferenceEmitKind.Failed) {
return null;
}
const emitted = emittedRef.expression;
if (emitted instanceof compiler.WrappedNodeExpr) {
if (refTo.node === inContext) {
// Suppress self-imports since components do not have to import themselves.
return null;
}
let isForwardReference = false;
if (emitted.node.getStart() > inContext.getStart()) {
const declaration = this.programDriver
.getProgram()
.getTypeChecker()
.getTypeAtLocation(emitted.node)
.getSymbol()?.declarations?.[0];
if (declaration && declaration.getSourceFile() === inContext.getSourceFile()) {
isForwardReference = true;
}
}
// An appropriate identifier is already in scope.
return { kind, symbolName: emitted.node.text, isForwardReference };
}
else if (emitted instanceof compiler.ExternalExpr &&
emitted.value.moduleName !== null &&
emitted.value.name !== null) {
return {
kind,
moduleSpecifier: emitted.value.moduleName,
symbolName: emitted.value.name,
isForwardReference: false,
};
}
return null;
}
getPotentialImportsFor(toImport, inContext, importMode) {
const imports = [];
const meta = this.metaReader.getDirectiveMetadata(toImport) ?? this.metaReader.getPipeMetadata(toImport);
if (meta === null) {
return imports;
}
if (meta.isStandalone || importMode === exports.PotentialImportMode.ForceDirect) {
const emitted = this.emit(exports.PotentialImportKind.Standalone, toImport, inContext);
if (emitted !== null) {
imports.push(emitted);
}
}
const exportingNgModules = this.ngModuleIndex.getNgModulesExporting(meta.ref.node);
if (exportingNgModules !== null) {
for (const exporter of exportingNgModules) {
const emittedRef = this.emit(exports.PotentialImportKind.NgModule, exporter, inContext);
if (emittedRef !== null) {
imports.push(emittedRef);
}
}
}
return imports;
}
getScopeData(component) {
if (this.scopeCache.has(component)) {
return this.scopeCache.get(component);
}
if (!isNamedClassDeclaration(component)) {
throw new Error(`AssertionError: components must have names`);
}
const scope = this.componentScopeReader.getScopeForComponent(component);
if (scope === null) {
return null;
}
const dependencies = scope.kind === exports.ComponentScopeKind.NgModule
? scope.compilation.dependencies
: scope.dependencies;
const data = {
directives: [],
pipes: [],
isPoisoned: scope.kind === exports.ComponentScopeKind.NgModule
? scope.compilation.isPoisoned
: scope.isPoisoned,
};
const typeChecker = this.programDriver.getProgram().getTypeChecker();
for (const dep of dependencies) {
if (dep.kind === exports.MetaKind.Directive) {
const dirScope = this.scopeDataOfDirectiveMeta(typeChecker, dep);
if (dirScope === null)
continue;
data.directives.push({ ...dirScope, isInScope: true });
}
else if (dep.kind === exports.MetaKind.Pipe) {
const pipeScope = this.scopeDataOfPipeMeta(typeChecker, dep);
if (pipeScope === null)
continue;
data.pipes.push({ ...pipeScope, isInScope: true });
}
}
this.scopeCache.set(component, data);
return data;
}
scopeDataOfDirectiveMeta(typeChecker, dep) {
if (dep.selector === null) {
// Skip this directive, it can't be added to a template anyway.
return null;
}
const tsSymbol = typeChecker.getSymbolAtLocation(dep.ref.node.name);
if (!isSymbolWithValueDeclaration(tsSymbol)) {
return null;
}
let ngModule = null;
const moduleScopeOfDir = this.componentScopeReader.getScopeForComponent(dep.ref.node);
if (moduleScopeOfDir !== null && moduleScopeOfDir.kind === exports.ComponentScopeKind.NgModule) {
ngModule = moduleScopeOfDir.ngModule;
}
return {
ref: dep.ref,
isComponent: dep.isComponent,
isStructural: dep.isStructural,
selector: dep.selector,
tsSymbol,
ngModule,
};
}
scopeDataOfPipeMeta(typeChecker, dep) {
const tsSymbol = typeChecker.getSymbolAtLocation(dep.ref.node.name);
if (tsSymbol === undefined) {
return null;
}
return {
ref: dep.ref,
name: dep.name,
tsSymbol,
};
}
}
function convertDiagnostic(diag, sourceResolver) {
if (!shouldReportDiagnostic(diag)) {
return null;
}
return translateDiagnostic(diag, sourceResolver);
}
/**
* Drives a `TypeCheckContext` to generate type-checking code for every component in the program.
*/
class WholeProgramTypeCheckingHost {
impl;
constructor(impl) {
this.impl = impl;
}
getSourceManager(sfPath) {
return this.impl.getFileData(sfPath).sourceManager;
}
shouldCheckClass(node) {
const sfPath = absoluteFromSourceFile(node.getSourceFile());
const shimPath = TypeCheckShimGenerator.shimFor(sfPath);
const fileData = this.impl.getFileData(sfPath);
// The component needs to be checked unless the shim which would contain it already exists.
return !fileData.shimData.has(shimPath);
}
recordShimData(sfPath, data) {
const fileData = this.impl.getFileData(sfPath);
fileData.shimData.set(data.path, data);
if (data.hasInlines) {
fileData.hasInlines = true;
}
}
recordComplete(sfPath) {
this.impl.getFileData(sfPath).isComplete = true;
}
}
/**
* Drives a `TypeCheckContext` to generate type-checking code efficiently for a single input file.
*/
class SingleFileTypeCheckingHost {
sfPath;
fileData;
impl;
seenInlines = false;
constructor(sfPath, fileData, impl) {
this.sfPath = sfPath;
this.fileData = fileData;
this.impl = impl;
}
assertPath(sfPath) {
if (this.sfPath !== sfPath) {
throw new Error(`AssertionError: querying TypeCheckingHost outside of assigned file`);
}
}
getSourceManager(sfPath) {
this.assertPath(sfPath);
return this.fileData.sourceManager;
}
shouldCheckClass(node) {
if (this.sfPath !== absoluteFromSourceFile(node.getSourceFile())) {
return false;
}
const shimPath = TypeCheckShimGenerator.shimFor(this.sfPath);
// Only need to generate a TCB for the class if no shim exists for it currently.
return !this.fileData.shimData.has(shimPath);
}
recordShimData(sfPath, data) {
this.assertPath(sfPath);
// Previous type-checking state may have required the use of inlines (assuming they were
// supported). If the current operation also requires inlines, this presents a problem:
// generating new inlines may invalidate any old inlines that old state depends on.
//
// Rather than resolve this issue by tracking specific dependencies on inlines, if the new state
// relies on inlines, any old state that relied on them is simply cleared. This happens when the
// first new state that uses inlines is encountered.
if (data.hasInlines && !this.seenInlines) {
this.impl.clearAllShimDataUsingInlines();
this.seenInlines = true;
}
this.fileData.shimData.set(data.path, data);
if (data.hasInlines) {
this.fileData.hasInlines = true;
}
}
recordComplete(sfPath) {
this.assertPath(sfPath);
this.fileData.isComplete = true;
}
}
/**
* Drives a `TypeCheckContext` to generate type-checking code efficiently for only those components
* which map to a single shim of a single input file.
*/
class SingleShimTypeCheckingHost extends SingleFileTypeCheckingHost {
shimPath;
constructor(sfPath, fileData, impl, shimPath) {
super(sfPath, fileData, impl);
this.shimPath = shimPath;
}
shouldCheckNode(node) {
if (this.sfPath !== absoluteFromSourceFile(node.getSourceFile())) {
return false;
}
// Only generate a TCB for the component if it maps to the requested shim file.
const shimPath = TypeCheckShimGenerator.shimFor(this.sfPath);
if (shimPath !== this.shimPath) {
return false;
}
// Only need to generate a TCB for the class if no shim exists for it currently.
return !this.fileData.shimData.has(shimPath);
}
}
exports.AbsoluteModuleStrategy = AbsoluteModuleStrategy;
exports.COMPILER_ERRORS_WITH_GUIDES = COMPILER_ERRORS_WITH_GUIDES;
exports.ClassPropertyMapping = ClassPropertyMapping;
exports.CompoundMetadataReader = CompoundMetadataReader;
exports.DefaultImportTracker = DefaultImportTracker;
exports.DynamicValue = DynamicValue;
exports.EnumValue = EnumValue;
exports.FatalDiagnosticError = FatalDiagnosticError;
exports.INPUT_INITIALIZER_FN = INPUT_INITIALIZER_FN;
exports.ImportManager = ImportManager;
exports.LocalIdentifierStrategy = LocalIdentifierStrategy;
exports.LogicalFileSystem = LogicalFileSystem;
exports.LogicalProjectStrategy = LogicalProjectStrategy;
exports.MODEL_INITIALIZER_FN = MODEL_INITIALIZER_FN;
exports.NgOriginalFile = NgOriginalFile;
exports.NodeJSFileSystem = NodeJSFileSystem;
exports.OUTPUT_INITIALIZER_FNS = OUTPUT_INITIALIZER_FNS;
exports.QUERY_INITIALIZER_FNS = QUERY_INITIALIZER_FNS;
exports.Reference = Reference;
exports.ReferenceEmitter = ReferenceEmitter;
exports.RelativePathStrategy = RelativePathStrategy;
exports.StaticInterpreter = StaticInterpreter;
exports.SyntheticValue = SyntheticValue;
exports.TemplateTypeCheckerImpl = TemplateTypeCheckerImpl;
exports.Trait = Trait;
exports.TypeCheckShimGenerator = TypeCheckShimGenerator;
exports.TypeScriptReflectionHost = TypeScriptReflectionHost;
exports.UnifiedModulesStrategy = UnifiedModulesStrategy;
exports.absoluteFrom = absoluteFrom;
exports.absoluteFromSourceFile = absoluteFromSourceFile;
exports.assertLocalCompilationUnresolvedConst = assertLocalCompilationUnresolvedConst;
exports.assertSuccessfulReferenceEmit = assertSuccessfulReferenceEmit;
exports.checkInheritanceOfInjectable = checkInheritanceOfInjectable;
exports.combineResolvers = combineResolvers;
exports.compileResults = compileResults;
exports.copyFileShimData = copyFileShimData;
exports.createForwardRefResolver = createForwardRefResolver;
exports.createHostElement = createHostElement;
exports.createValueHasWrongTypeError = createValueHasWrongTypeError;
exports.dirname = dirname;
exports.entityNameToValue = entityNameToValue;
exports.extraReferenceFromTypeQuery = extraReferenceFromTypeQuery;
exports.extractDecoratorQueryMetadata = extractDecoratorQueryMetadata;
exports.extractDirectiveMetadata = extractDirectiveMetadata;
exports.extractDirectiveTypeCheckMeta = extractDirectiveTypeCheckMeta;
exports.extractHostBindingResources = extractHostBindingResources;
exports.extractReferencesFromType = extractReferencesFromType;
exports.findAngularDecorator = findAngularDecorator;
exports.flattenInheritedDirectiveMetadata = flattenInheritedDirectiveMetadata;
exports.getAngularDecorators = getAngularDecorators;
exports.getConstructorDependencies = getConstructorDependencies;
exports.getContainingImportDeclaration = getContainingImportDeclaration;
exports.getDefaultImportDeclaration = getDefaultImportDeclaration;
exports.getDirectiveDiagnostics = getDirectiveDiagnostics;
exports.getFileSystem = getFileSystem;
exports.getOriginNodeForDiagnostics = getOriginNodeForDiagnostics;
exports.getProviderDiagnostics = getProviderDiagnostics;
exports.getRootDirs = getRootDirs;
exports.getSourceFile = getSourceFile;
exports.getSourceFileOrNull = getSourceFileOrNull;
exports.getTemplateDiagnostics = getTemplateDiagnostics;
exports.getUndecoratedClassWithAngularFeaturesDiagnostic = getUndecoratedClassWithAngularFeaturesDiagnostic;
exports.getValidConstructorDependencies = getValidConstructorDependencies;
exports.hasInjectableFields = hasInjectableFields;
exports.identifierOfNode = identifierOfNode;
exports.isAbstractClassDeclaration = isAbstractClassDeclaration;
exports.isAliasImportDeclaration = isAliasImportDeclaration;
exports.isAngularCore = isAngularCore;
exports.isAngularCoreReferenceWithPotentialAliasing = isAngularCoreReferenceWithPotentialAliasing;
exports.isAngularDecorator = isAngularDecorator;
exports.isDtsPath = isDtsPath;
exports.isExpressionForwardReference = isExpressionForwardReference;
exports.isFatalDiagnosticError = isFatalDiagnosticError;
exports.isFileShimSourceFile = isFileShimSourceFile;
exports.isHostDirectiveMetaForGlobalMode = isHostDirectiveMetaForGlobalMode;
exports.isLocalRelativePath = isLocalRelativePath;
exports.isNamedClassDeclaration = isNamedClassDeclaration;
exports.isNonDeclarationTsPath = isNonDeclarationTsPath;
exports.isShim = isShim;
exports.join = join;
exports.loadIsReferencedAliasDeclarationPatch = loadIsReferencedAliasDeclarationPatch;
exports.makeDiagnostic = makeDiagnostic;
exports.makeDuplicateDeclarationError = makeDuplicateDeclarationError;
exports.makeRelatedInformation = makeRelatedInformation;
exports.ngErrorCode = ngErrorCode;
exports.nodeDebugInfo = nodeDebugInfo;
exports.nodeNameForError = nodeNameForError;
exports.parseDecoratorInputTransformFunction = parseDecoratorInputTransformFunction;
exports.parseDirectiveStyles = parseDirectiveStyles;
exports.presetImportManagerForceNamespaceImports = presetImportManagerForceNamespaceImports;
exports.queryDecoratorNames = queryDecoratorNames;
exports.readBaseClass = readBaseClass;
exports.readBooleanType = readBooleanType;
exports.readMapType = readMapType;
exports.readStringArrayType = readStringArrayType;
exports.readStringType = readStringType;
exports.reflectClassMember = reflectClassMember;
exports.reflectObjectLiteral = reflectObjectLiteral;
exports.relative = relative;
exports.resolve = resolve;
exports.resolveImportedFile = resolveImportedFile;
exports.resolveModuleName = resolveModuleName;
exports.resolveProvidersRequiringFactory = resolveProvidersRequiringFactory;
exports.retagAllTsFiles = retagAllTsFiles;
exports.setFileSystem = setFileSystem;
exports.sfExtensionData = sfExtensionData;
exports.stripExtension = stripExtension;
exports.toFactoryMetadata = toFactoryMetadata;
exports.toR3Reference = toR3Reference;
exports.toRelativeImport = toRelativeImport;
exports.toUnredirectedSourceFile = toUnredirectedSourceFile;
exports.translateExpression = translateExpression;
exports.translateStatement = translateStatement;
exports.translateType = translateType;
exports.tryParseInitializerApi = tryParseInitializerApi;
exports.tryParseInitializerBasedOutput = tryParseInitializerBasedOutput;
exports.tryParseSignalInputMapping = tryParseSignalInputMapping;
exports.tryParseSignalModelMapping = tryParseSignalModelMapping;
exports.tryParseSignalQueryFromInitializer = tryParseSignalQueryFromInitializer;
exports.tryUnwrapForwardRef = tryUnwrapForwardRef;
exports.typeNodeToValueExpr = typeNodeToValueExpr;
exports.untagAllTsFiles = untagAllTsFiles;
exports.unwrapConstructorDependencies = unwrapConstructorDependencies;
exports.unwrapExpression = unwrapExpression;
exports.validateConstructorDependencies = validateConstructorDependencies;
exports.validateHostDirectives = validateHostDirectives;
exports.valueReferenceToExpression = valueReferenceToExpression;
exports.wrapFunctionExpressionsInParens = wrapFunctionExpressionsInParens;
exports.wrapTypeReference = wrapTypeReference;