refactor(compiler-cli): Add flag to TCB for non-diagnostic requests (#40071)
The TCB utility functions used to find nodes in the TCB are currently configured to ignore results when an ignore marker is found. However, these ignore markers are only meant to affect diagnostics requests. The Language Service may have a need to find nodes with diagnostic ignore markers. The most common example of this would be finding references for generic directives. The reference appears to the generic directive's class appears on the type ctor in the TCB, which is ignored for diagnostic purposes. These functions should only skip results when the request is in the context of a larger request for _diagnostics_. In all other cases, we should get matches, even if a diagnostic ignore marker is encountered. PR Close #40071
This commit is contained in:
parent
81718769c4
commit
6e4e68cb30
|
@ -121,12 +121,12 @@ export class TemplateTypeCheckerImpl implements TemplateTypeChecker {
|
|||
throw new Error(`Error: no shim file in program: ${shimPath}`);
|
||||
}
|
||||
|
||||
let tcb: ts.Node|null = findTypeCheckBlock(shimSf, id);
|
||||
let tcb: ts.Node|null = findTypeCheckBlock(shimSf, id, /*isDiagnosticsRequest*/ false);
|
||||
|
||||
if (tcb === null) {
|
||||
// Try for an inline block.
|
||||
const inlineSf = getSourceFileOrError(program, sfPath);
|
||||
tcb = findTypeCheckBlock(inlineSf, id);
|
||||
tcb = findTypeCheckBlock(inlineSf, id, /*isDiagnosticsRequest*/ false);
|
||||
}
|
||||
|
||||
let data: TemplateData|null = null;
|
||||
|
@ -194,7 +194,8 @@ export class TemplateTypeCheckerImpl implements TemplateTypeChecker {
|
|||
if (shimSf === undefined) {
|
||||
return null;
|
||||
}
|
||||
return getTemplateMapping(shimSf, positionInShimFile, fileRecord.sourceManager);
|
||||
return getTemplateMapping(
|
||||
shimSf, positionInShimFile, fileRecord.sourceManager, /*isDiagnosticsRequest*/ false);
|
||||
}
|
||||
|
||||
generateAllTypeCheckBlocks() {
|
||||
|
|
|
@ -91,7 +91,8 @@ export function translateDiagnostic(
|
|||
if (diagnostic.file === undefined || diagnostic.start === undefined) {
|
||||
return null;
|
||||
}
|
||||
const fullMapping = getTemplateMapping(diagnostic.file, diagnostic.start, resolver);
|
||||
const fullMapping = getTemplateMapping(
|
||||
diagnostic.file, diagnostic.start, resolver, /*isDiagnosticsRequest*/ true);
|
||||
if (fullMapping === null) {
|
||||
return null;
|
||||
}
|
||||
|
|
|
@ -54,10 +54,10 @@ export function requiresInlineTypeCheckBlock(node: ClassDeclaration<ts.ClassDecl
|
|||
|
||||
/** Maps a shim position back to a template location. */
|
||||
export function getTemplateMapping(
|
||||
shimSf: ts.SourceFile, position: number, resolver: TemplateSourceResolver): FullTemplateMapping|
|
||||
null {
|
||||
shimSf: ts.SourceFile, position: number, resolver: TemplateSourceResolver,
|
||||
isDiagnosticRequest: boolean): FullTemplateMapping|null {
|
||||
const node = getTokenAtPosition(shimSf, position);
|
||||
const sourceLocation = findSourceLocation(node, shimSf);
|
||||
const sourceLocation = findSourceLocation(node, shimSf, isDiagnosticRequest);
|
||||
if (sourceLocation === null) {
|
||||
return null;
|
||||
}
|
||||
|
@ -72,9 +72,10 @@ export function getTemplateMapping(
|
|||
return {sourceLocation, templateSourceMapping: mapping, span};
|
||||
}
|
||||
|
||||
export function findTypeCheckBlock(file: ts.SourceFile, id: TemplateId): ts.Node|null {
|
||||
export function findTypeCheckBlock(
|
||||
file: ts.SourceFile, id: TemplateId, isDiagnosticRequest: boolean): ts.Node|null {
|
||||
for (const stmt of file.statements) {
|
||||
if (ts.isFunctionDeclaration(stmt) && getTemplateId(stmt, file) === id) {
|
||||
if (ts.isFunctionDeclaration(stmt) && getTemplateId(stmt, file, isDiagnosticRequest) === id) {
|
||||
return stmt;
|
||||
}
|
||||
}
|
||||
|
@ -84,12 +85,14 @@ export function findTypeCheckBlock(file: ts.SourceFile, id: TemplateId): ts.Node
|
|||
/**
|
||||
* Traverses up the AST starting from the given node to extract the source location from comments
|
||||
* that have been emitted into the TCB. If the node does not exist within a TCB, or if an ignore
|
||||
* marker comment is found up the tree, this function returns null.
|
||||
* marker comment is found up the tree (and this is part of a diagnostic request), this function
|
||||
* returns null.
|
||||
*/
|
||||
export function findSourceLocation(node: ts.Node, sourceFile: ts.SourceFile): SourceLocation|null {
|
||||
export function findSourceLocation(
|
||||
node: ts.Node, sourceFile: ts.SourceFile, isDiagnosticsRequest: boolean): SourceLocation|null {
|
||||
// Search for comments until the TCB's function declaration is encountered.
|
||||
while (node !== undefined && !ts.isFunctionDeclaration(node)) {
|
||||
if (hasIgnoreForDiagnosticsMarker(node, sourceFile)) {
|
||||
if (hasIgnoreForDiagnosticsMarker(node, sourceFile) && isDiagnosticsRequest) {
|
||||
// There's an ignore marker on this node, so the diagnostic should not be reported.
|
||||
return null;
|
||||
}
|
||||
|
@ -98,7 +101,7 @@ export function findSourceLocation(node: ts.Node, sourceFile: ts.SourceFile): So
|
|||
if (span !== null) {
|
||||
// Once the positional information has been extracted, search further up the TCB to extract
|
||||
// the unique id that is attached with the TCB's function declaration.
|
||||
const id = getTemplateId(node, sourceFile);
|
||||
const id = getTemplateId(node, sourceFile, isDiagnosticsRequest);
|
||||
if (id === null) {
|
||||
return null;
|
||||
}
|
||||
|
@ -111,10 +114,11 @@ export function findSourceLocation(node: ts.Node, sourceFile: ts.SourceFile): So
|
|||
return null;
|
||||
}
|
||||
|
||||
function getTemplateId(node: ts.Node, sourceFile: ts.SourceFile): TemplateId|null {
|
||||
function getTemplateId(
|
||||
node: ts.Node, sourceFile: ts.SourceFile, isDiagnosticRequest: boolean): TemplateId|null {
|
||||
// Walk up to the function declaration of the TCB, the file information is attached there.
|
||||
while (!ts.isFunctionDeclaration(node)) {
|
||||
if (hasIgnoreForDiagnosticsMarker(node, sourceFile)) {
|
||||
if (hasIgnoreForDiagnosticsMarker(node, sourceFile) && isDiagnosticRequest) {
|
||||
// There's an ignore marker on this node, so the diagnostic should not be reported.
|
||||
return null;
|
||||
}
|
||||
|
|
|
@ -713,6 +713,23 @@ describe('find references', () => {
|
|||
assertTextSpans(refs, ['<div dir>', 'Dir', 'Dir2']);
|
||||
assertFileNames(refs, ['app.ts', 'dir.ts', 'dir2.ts']);
|
||||
});
|
||||
|
||||
it('should be able to request references for generic directives', () => {
|
||||
const {text, cursor} = extractCursorInfo(`
|
||||
import {Component, NgModule} from '@angular/core';
|
||||
|
||||
@Component({template: '<div *ngF¦or="let item of items"></div>'})
|
||||
export class AppCmp {
|
||||
items = [];
|
||||
}
|
||||
`);
|
||||
const appFile = {name: _('/app.ts'), contents: text};
|
||||
env = createModuleWithDeclarations([appFile]);
|
||||
const refs = getReferencesAtPosition(_('/app.ts'), cursor)!;
|
||||
expect(refs.length).toBe(6);
|
||||
assertTextSpans(refs, ['<div *ngFor="let item of items"></div>', 'NgForOf']);
|
||||
assertFileNames(refs, ['index.d.ts', 'app.ts']);
|
||||
});
|
||||
});
|
||||
|
||||
describe('components', () => {
|
||||
|
|
Loading…
Reference in New Issue