refactor(compiler-cli): track whether a TcbPosition corresponds with a shim file (#45454)

Extends `TcbPosition` with a field that indicates whether the `tcbPath` is a
type-checking shim file, or an original source file with an inline type check
block.

This field is used in an upcoming commit that fixes an inconsistency with how
inline type check blocks are incorrectly interpreted as a type-checking shim
file instead.

PR Close #45454
This commit is contained in:
JoostK
2022-03-26 22:23:28 +01:00
committed by Dylan Hunn
parent b2758d781b
commit ff32301bf5
8 changed files with 100 additions and 26 deletions
@@ -50,6 +50,11 @@ export interface TcbLocation {
*/
tcbPath: AbsoluteFsPath;
/**
* Whether the type check block exists in a type-checking shim file or is inline.
*/
isShimFile: boolean;
/** The location in the file where node appears. */
positionInFile: number;
}
@@ -97,7 +97,7 @@ export class TemplateTypeCheckerImpl implements TemplateTypeChecker {
}
private getLatestComponentState(component: ts.ClassDeclaration):
{data: TemplateData|null, tcb: ts.Node|null, shimPath: AbsoluteFsPath} {
{data: TemplateData|null, tcb: ts.Node|null, tcbPath: AbsoluteFsPath, tcbIsShim: boolean} {
this.ensureShimForComponent(component);
const sf = component.getSourceFile();
@@ -107,7 +107,7 @@ export class TemplateTypeCheckerImpl implements TemplateTypeChecker {
const fileRecord = this.getFileData(sfPath);
if (!fileRecord.shimData.has(shimPath)) {
return {data: null, tcb: null, shimPath};
return {data: null, tcb: null, tcbPath: shimPath, tcbIsShim: true};
}
const templateId = fileRecord.sourceManager.getTemplateId(component);
@@ -123,10 +123,15 @@ export class TemplateTypeCheckerImpl implements TemplateTypeChecker {
let tcb: ts.Node|null = 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: TemplateData|null = null;
@@ -134,7 +139,7 @@ export class TemplateTypeCheckerImpl implements TemplateTypeChecker {
data = shimRecord.templates.get(templateId)!;
}
return {data, tcb, shimPath};
return {data, tcb, tcbPath, tcbIsShim: tcbPath === shimPath};
}
isTrackedTypeCheckFile(filePath: AbsoluteFsPath): boolean {
@@ -334,12 +339,12 @@ export class TemplateTypeCheckerImpl implements TemplateTypeChecker {
return this.completionCache.get(component)!;
}
const {tcb, data, shimPath} = this.getLatestComponentState(component);
const {tcb, data, tcbPath, tcbIsShim} = this.getLatestComponentState(component);
if (tcb === null || data === null) {
return null;
}
const engine = new CompletionEngine(tcb, data, shimPath);
const engine = new CompletionEngine(tcb, data, tcbPath, tcbIsShim);
this.completionCache.set(component, engine);
return engine;
}
@@ -509,13 +514,13 @@ export class TemplateTypeCheckerImpl implements TemplateTypeChecker {
return this.symbolBuilderCache.get(component)!;
}
const {tcb, data, shimPath} = this.getLatestComponentState(component);
const {tcb, data, tcbPath, tcbIsShim} = this.getLatestComponentState(component);
if (tcb === null || data === null) {
return null;
}
const builder = new SymbolBuilder(
shimPath, tcb, data, this.componentScopeReader,
tcbPath, tcbIsShim, tcb, data, this.componentScopeReader,
() => this.programDriver.getProgram().getTypeChecker());
this.symbolBuilderCache.set(component, builder);
return builder;
@@ -35,7 +35,9 @@ export class CompletionEngine {
new Map<PropertyRead|SafePropertyRead|LiteralPrimitive|TmplAstTextAttribute, TcbLocation>();
constructor(private tcb: ts.Node, private data: TemplateData, private tcbPath: AbsoluteFsPath) {
constructor(
private tcb: ts.Node, private data: TemplateData, private tcbPath: AbsoluteFsPath,
private tcbIsShim: boolean) {
// Find the component completion expression within the TCB. This looks like: `ctx. /* ... */;`
const globalRead = findFirstMatchingNode(this.tcb, {
filter: ts.isPropertyAccessExpression,
@@ -45,6 +47,7 @@ export class CompletionEngine {
if (globalRead !== null) {
this.componentContext = {
tcbPath: this.tcbPath,
isShimFile: this.tcbIsShim,
// `globalRead.name` is an empty `ts.Identifier`, so its start position immediately follows
// the `.` in `ctx.`. TS autocompletion APIs can then be used to access completion results
// for the component context.
@@ -83,6 +86,7 @@ export class CompletionEngine {
if (nodeLocation !== null) {
nodeContext = {
tcbPath: this.tcbPath,
isShimFile: this.tcbIsShim,
positionInFile: nodeLocation.getStart(),
};
}
@@ -96,6 +100,7 @@ export class CompletionEngine {
if (nodeLocation) {
nodeContext = {
tcbPath: this.tcbPath,
isShimFile: this.tcbIsShim,
positionInFile: nodeLocation.getStart(),
};
}
@@ -148,6 +153,7 @@ export class CompletionEngine {
const res: TcbLocation = {
tcbPath: this.tcbPath,
isShimFile: this.tcbIsShim,
positionInFile: tsExpr.name.getEnd(),
};
this.expressionCompletionCache.set(expr, res);
@@ -188,6 +194,7 @@ export class CompletionEngine {
}
const res: TcbLocation = {
tcbPath: this.tcbPath,
isShimFile: this.tcbIsShim,
positionInFile: positionInShimFile,
};
this.expressionCompletionCache.set(expr, res);
@@ -29,7 +29,8 @@ export class SymbolBuilder {
private symbolCache = new Map<AST|TmplAstNode, Symbol|null>();
constructor(
private readonly shimPath: AbsoluteFsPath,
private readonly tcbPath: AbsoluteFsPath,
private readonly tcbIsShim: boolean,
private readonly typeCheckBlock: ts.Node,
private readonly templateData: TemplateData,
private readonly componentScopeReader: ComponentScopeReader,
@@ -233,7 +234,7 @@ export class SymbolBuilder {
const addEventListener = outputFieldAccess.name;
const tsSymbol = this.getTypeChecker().getSymbolAtLocation(addEventListener);
const tsType = this.getTypeChecker().getTypeAtLocation(addEventListener);
const positionInShimFile = this.getShimPositionForNode(addEventListener);
const positionInFile = this.getTcbPositionForNode(addEventListener);
const target = this.getSymbol(consumer);
if (target === null || tsSymbol === undefined) {
@@ -245,7 +246,11 @@ export class SymbolBuilder {
tsSymbol,
tsType,
target,
tcbLocation: {tcbPath: this.shimPath, positionInFile: positionInShimFile},
tcbLocation: {
tcbPath: this.tcbPath,
isShimFile: this.tcbIsShim,
positionInFile,
},
});
} else {
if (!ts.isElementAccessExpression(outputFieldAccess)) {
@@ -263,14 +268,18 @@ export class SymbolBuilder {
continue;
}
const positionInShimFile = this.getShimPositionForNode(outputFieldAccess);
const positionInFile = this.getTcbPositionForNode(outputFieldAccess);
const tsType = this.getTypeChecker().getTypeAtLocation(outputFieldAccess);
bindings.push({
kind: SymbolKind.Binding,
tsSymbol,
tsType,
target,
tcbLocation: {tcbPath: this.shimPath, positionInFile: positionInShimFile},
tcbLocation: {
tcbPath: this.tcbPath,
isShimFile: this.tcbIsShim,
positionInFile,
},
});
}
}
@@ -383,8 +392,9 @@ export class SymbolBuilder {
kind: SymbolKind.Variable,
declaration: variable,
localVarLocation: {
tcbPath: this.shimPath,
positionInFile: this.getShimPositionForNode(node.name),
tcbPath: this.tcbPath,
isShimFile: this.tcbIsShim,
positionInFile: this.getTcbPositionForNode(node.name),
}
};
}
@@ -415,8 +425,9 @@ export class SymbolBuilder {
}
const referenceVarTcbLocation: TcbLocation = {
tcbPath: this.shimPath,
positionInFile: this.getShimPositionForNode(node),
tcbPath: this.tcbPath,
isShimFile: this.tcbIsShim,
positionInFile: this.getTcbPositionForNode(node),
};
if (target instanceof TmplAstTemplate || target instanceof TmplAstElement) {
return {
@@ -561,7 +572,7 @@ export class SymbolBuilder {
tsSymbol = this.getTypeChecker().getSymbolAtLocation(node);
}
const positionInShimFile = this.getShimPositionForNode(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.
@@ -569,13 +580,17 @@ export class SymbolBuilder {
// Examples of this would be literals and `document.createElement('div')`.
tsSymbol: tsSymbol ?? type.symbol ?? null,
tsType: type,
tcbLocation: {tcbPath: this.shimPath, positionInFile: positionInShimFile},
tcbLocation: {
tcbPath: this.tcbPath,
isShimFile: this.tcbIsShim,
positionInFile,
},
};
}
private getShimPositionForNode(node: ts.Node): number {
private getTcbPositionForNode(node: ts.Node): number {
if (ts.isTypeReferenceNode(node)) {
return this.getShimPositionForNode(node.typeName);
return this.getTcbPositionForNode(node.typeName);
} else if (ts.isQualifiedName(node)) {
return node.right.getStart();
} else if (ts.isPropertyAccessExpression(node)) {
@@ -1581,6 +1581,48 @@ runInEachFileSystem(() => {
symbol.directives.map(dir => program.getTypeChecker().typeToString(dir.tsType)).sort();
expect(actualDirectives).toEqual(['GenericDir<any>']);
});
it('has correct tcb location for components with inline TCBs', () => {
const fileName = absoluteFrom('/main.ts');
const {program, templateTypeChecker} = baseTestSetup(
[
{
fileName,
templates: {'Cmp': '<div></div>'},
// Force an inline TCB by using a non-exported component class
source: `class Cmp {}`,
},
],
{inlining: true, config: {enableTemplateTypeChecker: true}});
const sf = getSourceFileOrError(program, fileName);
const cmp = getClass(sf, 'Cmp');
const nodes = templateTypeChecker.getTemplate(cmp)!;
const symbol = templateTypeChecker.getSymbolOfNode(nodes[0] as TmplAstElement, cmp)!;
assertElementSymbol(symbol);
expect(symbol.tcbLocation.tcbPath).toBe(sf.fileName);
expect(symbol.tcbLocation.isShimFile).toBe(false);
});
it('has correct tcb location for components with TCBs in a type-checking shim file', () => {
const fileName = absoluteFrom('/main.ts');
const {program, templateTypeChecker} = setup([
{
fileName,
templates: {'Cmp': '<div></div>'},
},
]);
const sf = getSourceFileOrError(program, fileName);
const cmp = getClass(sf, 'Cmp');
const nodes = templateTypeChecker.getTemplate(cmp)!;
const symbol = templateTypeChecker.getSymbolOfNode(nodes[0] as TmplAstElement, cmp)!;
assertElementSymbol(symbol);
expect(symbol.tcbLocation.tcbPath).not.toBe(sf.fileName);
expect(symbol.tcbLocation.isShimFile).toBe(true);
});
});
});
+1 -1
View File
@@ -110,7 +110,7 @@ export class DefinitionBuilder {
const tcbLocation = symbol.kind === SymbolKind.Variable ? symbol.localVarLocation :
symbol.referenceVarLocation;
const mapping = getTemplateLocationFromTcbLocation(
this.compiler.getTemplateTypeChecker(), tcbLocation.tcbPath,
this.compiler.getTemplateTypeChecker(), tcbLocation.tcbPath, tcbLocation.isShimFile,
tcbLocation.positionInFile);
if (mapping !== null) {
definitions.push({
@@ -222,7 +222,7 @@ export function convertToTemplateDocumentSpan<T extends ts.DocumentSpan>(
// serverHost or LSParseConfigHost in the adapter. We should have a better defined way to
// normalize paths.
const mapping = getTemplateLocationFromTcbLocation(
templateTypeChecker, absoluteFrom(shimDocumentSpan.fileName),
templateTypeChecker, absoluteFrom(shimDocumentSpan.fileName), /* tcbIsShim */ true,
shimDocumentSpan.textSpan.start);
if (mapping === null) {
return null;
+3 -3
View File
@@ -363,10 +363,10 @@ export function isWithin(position: number, span: AbsoluteSourceSpan|ParseSourceS
* the span in the template.
*/
export function getTemplateLocationFromTcbLocation(
templateTypeChecker: TemplateTypeChecker, shimPath: AbsoluteFsPath,
positionInShimFile: number): {templateUrl: AbsoluteFsPath, span: ParseSourceSpan}|null {
templateTypeChecker: TemplateTypeChecker, tcbPath: AbsoluteFsPath, tcbIsShim: boolean,
positionInFile: number): {templateUrl: AbsoluteFsPath, span: ParseSourceSpan}|null {
const mapping = templateTypeChecker.getTemplateMappingAtTcbLocation(
{tcbPath: shimPath, positionInFile: positionInShimFile});
{tcbPath, isShimFile: tcbIsShim, positionInFile});
if (mapping === null) {
return null;
}