fix(language-service): Do not produce diagnostics if metadata for NgModule not found (#34113)
The language service incorrectly reports an error if it fails to find NgModule metadata for a particular Component / Directive. In many cases, the use case is legit, particularly in test. This commit removes such diagnostic message and cleans up the interface for `TypeScriptHost.getTemplateAst()`. PR closes https://github.com/angular/vscode-ng-language-service/issues/463 PR Close #34113
This commit is contained in:
parent
c16a79df5c
commit
39722df41e
|
@ -8,7 +8,7 @@
|
||||||
|
|
||||||
import {CompileDirectiveMetadata, CompileDirectiveSummary, CompilePipeSummary, CssSelector, Node as HtmlAst, ParseError, Parser, TemplateAst} from '@angular/compiler';
|
import {CompileDirectiveMetadata, CompileDirectiveSummary, CompilePipeSummary, CssSelector, Node as HtmlAst, ParseError, Parser, TemplateAst} from '@angular/compiler';
|
||||||
|
|
||||||
import {Diagnostic, TemplateSource} from './types';
|
import {TemplateSource} from './types';
|
||||||
|
|
||||||
export interface AstResult {
|
export interface AstResult {
|
||||||
htmlAst: HtmlAst[];
|
htmlAst: HtmlAst[];
|
||||||
|
@ -25,7 +25,3 @@ export type SelectorInfo = {
|
||||||
selectors: CssSelector[],
|
selectors: CssSelector[],
|
||||||
map: Map<CssSelector, CompileDirectiveSummary>
|
map: Map<CssSelector, CompileDirectiveSummary>
|
||||||
};
|
};
|
||||||
|
|
||||||
export function isAstResult(result: AstResult | Diagnostic): result is AstResult {
|
|
||||||
return result.hasOwnProperty('templateAst');
|
|
||||||
}
|
|
||||||
|
|
|
@ -8,7 +8,6 @@
|
||||||
|
|
||||||
import * as tss from 'typescript/lib/tsserverlibrary';
|
import * as tss from 'typescript/lib/tsserverlibrary';
|
||||||
|
|
||||||
import {isAstResult} from './common';
|
|
||||||
import {getTemplateCompletions} from './completions';
|
import {getTemplateCompletions} from './completions';
|
||||||
import {getDefinitionAndBoundSpan, getTsDefinitionAndBoundSpan} from './definitions';
|
import {getDefinitionAndBoundSpan, getTsDefinitionAndBoundSpan} from './definitions';
|
||||||
import {getDeclarationDiagnostics, getTemplateDiagnostics, ngDiagnosticToTsDiagnostic, uniqueBySpan} from './diagnostics';
|
import {getDeclarationDiagnostics, getTemplateDiagnostics, ngDiagnosticToTsDiagnostic, uniqueBySpan} from './diagnostics';
|
||||||
|
@ -34,11 +33,9 @@ class LanguageServiceImpl implements LanguageService {
|
||||||
const templates = this.host.getTemplates(fileName);
|
const templates = this.host.getTemplates(fileName);
|
||||||
|
|
||||||
for (const template of templates) {
|
for (const template of templates) {
|
||||||
const astOrDiagnostic = this.host.getTemplateAst(template);
|
const ast = this.host.getTemplateAst(template);
|
||||||
if (isAstResult(astOrDiagnostic)) {
|
if (ast) {
|
||||||
results.push(...getTemplateDiagnostics(astOrDiagnostic));
|
results.push(...getTemplateDiagnostics(ast));
|
||||||
} else {
|
|
||||||
results.push(astOrDiagnostic);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -8,7 +8,6 @@
|
||||||
|
|
||||||
import * as ts from 'typescript';
|
import * as ts from 'typescript';
|
||||||
|
|
||||||
import {isAstResult} from './common';
|
|
||||||
import {createGlobalSymbolTable} from './global_symbols';
|
import {createGlobalSymbolTable} from './global_symbols';
|
||||||
import * as ng from './types';
|
import * as ng from './types';
|
||||||
import {TypeScriptServiceHost} from './typescript_host';
|
import {TypeScriptServiceHost} from './typescript_host';
|
||||||
|
@ -73,7 +72,7 @@ abstract class BaseTemplate implements ng.TemplateSource {
|
||||||
// TODO: There is circular dependency here between TemplateSource and
|
// TODO: There is circular dependency here between TemplateSource and
|
||||||
// TypeScriptHost. Consider refactoring the code to break this cycle.
|
// TypeScriptHost. Consider refactoring the code to break this cycle.
|
||||||
const ast = this.host.getTemplateAst(this);
|
const ast = this.host.getTemplateAst(this);
|
||||||
const pipes = isAstResult(ast) ? ast.pipes : [];
|
const pipes = (ast && ast.pipes) || [];
|
||||||
return getPipesTable(sourceFile, program, typeChecker, pipes);
|
return getPipesTable(sourceFile, program, typeChecker, pipes);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -193,7 +193,7 @@ export interface LanguageServiceHost {
|
||||||
/**
|
/**
|
||||||
* Return the AST for both HTML and template for the contextFile.
|
* Return the AST for both HTML and template for the contextFile.
|
||||||
*/
|
*/
|
||||||
getTemplateAst(template: TemplateSource): AstResult|Diagnostic;
|
getTemplateAst(template: TemplateSource): AstResult|undefined;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return the template AST for the node that corresponds to the position.
|
* Return the template AST for the node that corresponds to the position.
|
||||||
|
|
|
@ -11,11 +11,11 @@ import {SchemaMetadata, ViewEncapsulation, ɵConsole as Console} from '@angular/
|
||||||
import * as ts from 'typescript';
|
import * as ts from 'typescript';
|
||||||
import * as tss from 'typescript/lib/tsserverlibrary';
|
import * as tss from 'typescript/lib/tsserverlibrary';
|
||||||
|
|
||||||
import {AstResult, isAstResult} from './common';
|
import {AstResult} from './common';
|
||||||
import {createLanguageService} from './language_service';
|
import {createLanguageService} from './language_service';
|
||||||
import {ReflectorHost} from './reflector_host';
|
import {ReflectorHost} from './reflector_host';
|
||||||
import {ExternalTemplate, InlineTemplate, getClassDeclFromDecoratorProp, getPropertyAssignmentFromValue} from './template';
|
import {ExternalTemplate, InlineTemplate, getClassDeclFromDecoratorProp, getPropertyAssignmentFromValue} from './template';
|
||||||
import {Declaration, DeclarationError, Diagnostic, DiagnosticKind, DiagnosticMessageChain, LanguageService, LanguageServiceHost, Span, TemplateSource} from './types';
|
import {Declaration, DeclarationError, DiagnosticMessageChain, LanguageService, LanguageServiceHost, Span, TemplateSource} from './types';
|
||||||
import {findTightestNode, getDirectiveClassLike} from './utils';
|
import {findTightestNode, getDirectiveClassLike} from './utils';
|
||||||
|
|
||||||
|
|
||||||
|
@ -456,11 +456,7 @@ export class TypeScriptServiceHost implements LanguageServiceHost {
|
||||||
if (!template) {
|
if (!template) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const astResult = this.getTemplateAst(template);
|
return this.getTemplateAst(template);
|
||||||
if (!isAstResult(astResult)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
return astResult;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -505,20 +501,14 @@ export class TypeScriptServiceHost implements LanguageServiceHost {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parse the `template` and return its AST if there's no error. Otherwise
|
* Parse the `template` and return its AST, if any.
|
||||||
* return a Diagnostic message.
|
|
||||||
* @param template template to be parsed
|
* @param template template to be parsed
|
||||||
*/
|
*/
|
||||||
getTemplateAst(template: TemplateSource): AstResult|Diagnostic {
|
getTemplateAst(template: TemplateSource): AstResult|undefined {
|
||||||
const {type: classSymbol, fileName} = template;
|
const {type: classSymbol, fileName} = template;
|
||||||
try {
|
|
||||||
const data = this.resolver.getNonNormalizedDirectiveMetadata(classSymbol);
|
const data = this.resolver.getNonNormalizedDirectiveMetadata(classSymbol);
|
||||||
if (!data) {
|
if (!data) {
|
||||||
return {
|
return;
|
||||||
kind: DiagnosticKind.Error,
|
|
||||||
message: `No metadata found for '${classSymbol.name}' in ${fileName}.`,
|
|
||||||
span: template.span,
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
const htmlParser = new I18NHtmlParser(new HtmlParser());
|
const htmlParser = new I18NHtmlParser(new HtmlParser());
|
||||||
const expressionParser = new Parser(new Lexer());
|
const expressionParser = new Parser(new Lexer());
|
||||||
|
@ -533,14 +523,9 @@ export class TypeScriptServiceHost implements LanguageServiceHost {
|
||||||
preserveLineEndings: true, // do not convert CRLF to LF
|
preserveLineEndings: true, // do not convert CRLF to LF
|
||||||
});
|
});
|
||||||
const {directives, pipes, schemas} = this.getModuleMetadataForDirective(classSymbol);
|
const {directives, pipes, schemas} = this.getModuleMetadataForDirective(classSymbol);
|
||||||
const parseResult =
|
const parseResult = parser.tryParseHtml(htmlResult, data.metadata, directives, pipes, schemas);
|
||||||
parser.tryParseHtml(htmlResult, data.metadata, directives, pipes, schemas);
|
|
||||||
if (!parseResult.templateAst) {
|
if (!parseResult.templateAst) {
|
||||||
return {
|
return;
|
||||||
kind: DiagnosticKind.Error,
|
|
||||||
message: `Failed to parse template for '${classSymbol.name}' in ${fileName}`,
|
|
||||||
span: template.span,
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
htmlAst: htmlResult.rootNodes,
|
htmlAst: htmlResult.rootNodes,
|
||||||
|
@ -548,14 +533,6 @@ export class TypeScriptServiceHost implements LanguageServiceHost {
|
||||||
directive: data.metadata, directives, pipes,
|
directive: data.metadata, directives, pipes,
|
||||||
parseErrors: parseResult.errors, expressionParser, template,
|
parseErrors: parseResult.errors, expressionParser, template,
|
||||||
};
|
};
|
||||||
} catch (e) {
|
|
||||||
return {
|
|
||||||
kind: DiagnosticKind.Error,
|
|
||||||
message: e.message,
|
|
||||||
span:
|
|
||||||
e.fileName === fileName && template.query.getSpanAt(e.line, e.column) || template.span,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -829,4 +829,20 @@ describe('diagnostics', () => {
|
||||||
expect(content.substring(start !, start ! + length !)).toBe(`line${i}`);
|
expect(content.substring(start !, start ! + length !)).toBe(`line${i}`);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should not produce diagnostics for non-exported directives', () => {
|
||||||
|
const fileName = '/app/test.component.ts';
|
||||||
|
mockHost.addScript(fileName, `
|
||||||
|
import {Component} from '@angular/core';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
template: '<test-comp></test-comp>'
|
||||||
|
})
|
||||||
|
class TestHostComponent {}
|
||||||
|
`);
|
||||||
|
const tsDiags = tsLS.getSemanticDiagnostics(fileName);
|
||||||
|
expect(tsDiags).toEqual([]);
|
||||||
|
const ngDiags = ngLS.getDiagnostics(fileName);
|
||||||
|
expect(ngDiags).toEqual([]);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in New Issue