fix(language-service): Prevent crashes on unemitable references (#47938)

Currently, when generating an import of a selector, the language service might crash if the compiler cannot emit a reference to the new symbol's file from the target component's file. (This might happen because the two are the same file.) We should handle that case by reusing the existing import if possible, or otherwise failing gracefully.

PR Close #47938
This commit is contained in:
Dylan Hunn
2022-11-01 20:08:16 -07:00
committed by Andrew Kushnir
parent 760cd78e94
commit ce8160ecb2
3 changed files with 23 additions and 7 deletions
@@ -18,7 +18,8 @@ import {SymbolWithValueDeclaration} from '../../util/src/typescript';
*/
export interface PotentialImport {
kind: PotentialImportKind;
moduleSpecifier: string;
// If no moduleSpecifier is present, the given symbol name is already in scope.
moduleSpecifier?: string;
symbolName: string;
}
@@ -6,7 +6,7 @@
* found in the LICENSE file at https://angular.io/license
*/
import {AST, CssSelector, DomElementSchemaRegistry, ExternalExpr, LiteralPrimitive, ParseSourceSpan, PropertyRead, SafePropertyRead, TmplAstElement, TmplAstNode, TmplAstReference, TmplAstTemplate, TmplAstTextAttribute} from '@angular/compiler';
import {AST, CssSelector, DomElementSchemaRegistry, ExternalExpr, LiteralPrimitive, ParseSourceSpan, PropertyRead, SafePropertyRead, TmplAstElement, TmplAstNode, TmplAstReference, TmplAstTemplate, TmplAstTextAttribute, WrappedNodeExpr} from '@angular/compiler';
import ts from 'typescript';
import {ErrorCode, ngErrorCode} from '../../diagnostics';
@@ -682,6 +682,7 @@ export class TemplateTypeCheckerImpl implements TemplateTypeChecker {
if (toImport.ngModule !== null) {
ngModuleRef = this.metaReader.getNgModuleMetadata(new Reference(toImport.ngModule))?.ref;
}
const kind = ngModuleRef ? PotentialImportKind.NgModule : PotentialImportKind.Standalone;
// Import the ngModule if one exists. Otherwise, import the standalone trait directly.
const importTarget = ngModuleRef ?? toImport.ref;
@@ -691,9 +692,17 @@ export class TemplateTypeCheckerImpl implements TemplateTypeChecker {
// ranking references, such as keeping a record of import specifiers used in existing code.
const emittedRef = this.refEmitter.emit(importTarget, inContext.getSourceFile());
if (emittedRef.kind === ReferenceEmitKind.Failed) return [];
const emittedExpression = emittedRef.expression;
// This is not be a true import if an appropriate identifier is already in scope.
if (emittedExpression instanceof WrappedNodeExpr) {
return [{kind, symbolName: emittedExpression.node.getText()}];
}
// Otherwise, it must be a genuine external expression.
if (!(emittedExpression instanceof ExternalExpr)) {
return [];
}
// The resulting import expression should have a module name and an identifier name.
const emittedExpression: ExternalExpr = emittedRef.expression as ExternalExpr;
if (emittedExpression.value.moduleName === null || emittedExpression.value.name === null)
return [];
@@ -93,10 +93,13 @@ function getCodeActions(
}
// Create a code action for this import.
let description = `Import ${importName}`;
if (potentialImport.moduleSpecifier !== undefined) {
description += ` from '${potentialImport.moduleSpecifier}' on ${importOn.name!.text}`;
}
codeActions.push({
fixName: FixIdForCodeFixesAll.FIX_MISSING_IMPORT,
description: `Import ${importName} from '${potentialImport.moduleSpecifier}' on ${
importOn.name!.text}`,
description,
changes: [{
fileName: importOn.getSourceFile().fileName,
textChanges: [...fileImportChanges, ...traitImportChanges],
@@ -115,7 +118,10 @@ function getCodeActions(
function updateImportsForTypescriptFile(
tsChecker: ts.TypeChecker, file: ts.SourceFile, newImport: PotentialImport,
tsFileToImport: ts.SourceFile): [ts.TextChange[], string] {
const changes = new Array<ts.TextChange>();
// If the expression is already imported, we can just return its name.
if (newImport.moduleSpecifier === undefined) {
return [[], newImport.symbolName];
}
// The trait might already be imported, possibly under a different name. If so, determine the
// local name of the imported trait.