Skip to content

Commit ce8160e

Browse files
dylhunnAndrewKushnir
authored andcommitted
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
1 parent 760cd78 commit ce8160e

File tree

3 files changed

+23
-7
lines changed

3 files changed

+23
-7
lines changed

packages/compiler-cli/src/ngtsc/typecheck/api/scope.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,8 @@ import {SymbolWithValueDeclaration} from '../../util/src/typescript';
1818
*/
1919
export interface PotentialImport {
2020
kind: PotentialImportKind;
21-
moduleSpecifier: string;
21+
// If no moduleSpecifier is present, the given symbol name is already in scope.
22+
moduleSpecifier?: string;
2223
symbolName: string;
2324
}
2425

packages/compiler-cli/src/ngtsc/typecheck/src/checker.ts

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
* found in the LICENSE file at https://angular.io/license
77
*/
88

9-
import {AST, CssSelector, DomElementSchemaRegistry, ExternalExpr, LiteralPrimitive, ParseSourceSpan, PropertyRead, SafePropertyRead, TmplAstElement, TmplAstNode, TmplAstReference, TmplAstTemplate, TmplAstTextAttribute} from '@angular/compiler';
9+
import {AST, CssSelector, DomElementSchemaRegistry, ExternalExpr, LiteralPrimitive, ParseSourceSpan, PropertyRead, SafePropertyRead, TmplAstElement, TmplAstNode, TmplAstReference, TmplAstTemplate, TmplAstTextAttribute, WrappedNodeExpr} from '@angular/compiler';
1010
import ts from 'typescript';
1111

1212
import {ErrorCode, ngErrorCode} from '../../diagnostics';
@@ -682,6 +682,7 @@ export class TemplateTypeCheckerImpl implements TemplateTypeChecker {
682682
if (toImport.ngModule !== null) {
683683
ngModuleRef = this.metaReader.getNgModuleMetadata(new Reference(toImport.ngModule))?.ref;
684684
}
685+
const kind = ngModuleRef ? PotentialImportKind.NgModule : PotentialImportKind.Standalone;
685686

686687
// Import the ngModule if one exists. Otherwise, import the standalone trait directly.
687688
const importTarget = ngModuleRef ?? toImport.ref;
@@ -691,9 +692,17 @@ export class TemplateTypeCheckerImpl implements TemplateTypeChecker {
691692
// ranking references, such as keeping a record of import specifiers used in existing code.
692693
const emittedRef = this.refEmitter.emit(importTarget, inContext.getSourceFile());
693694
if (emittedRef.kind === ReferenceEmitKind.Failed) return [];
695+
const emittedExpression = emittedRef.expression;
696+
697+
// This is not be a true import if an appropriate identifier is already in scope.
698+
if (emittedExpression instanceof WrappedNodeExpr) {
699+
return [{kind, symbolName: emittedExpression.node.getText()}];
700+
}
701+
// Otherwise, it must be a genuine external expression.
702+
if (!(emittedExpression instanceof ExternalExpr)) {
703+
return [];
704+
}
694705

695-
// The resulting import expression should have a module name and an identifier name.
696-
const emittedExpression: ExternalExpr = emittedRef.expression as ExternalExpr;
697706
if (emittedExpression.value.moduleName === null || emittedExpression.value.name === null)
698707
return [];
699708

packages/language-service/src/codefixes/fix_missing_import.ts

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -93,10 +93,13 @@ function getCodeActions(
9393
}
9494

9595
// Create a code action for this import.
96+
let description = `Import ${importName}`;
97+
if (potentialImport.moduleSpecifier !== undefined) {
98+
description += ` from '${potentialImport.moduleSpecifier}' on ${importOn.name!.text}`;
99+
}
96100
codeActions.push({
97101
fixName: FixIdForCodeFixesAll.FIX_MISSING_IMPORT,
98-
description: `Import ${importName} from '${potentialImport.moduleSpecifier}' on ${
99-
importOn.name!.text}`,
102+
description,
100103
changes: [{
101104
fileName: importOn.getSourceFile().fileName,
102105
textChanges: [...fileImportChanges, ...traitImportChanges],
@@ -115,7 +118,10 @@ function getCodeActions(
115118
function updateImportsForTypescriptFile(
116119
tsChecker: ts.TypeChecker, file: ts.SourceFile, newImport: PotentialImport,
117120
tsFileToImport: ts.SourceFile): [ts.TextChange[], string] {
118-
const changes = new Array<ts.TextChange>();
121+
// If the expression is already imported, we can just return its name.
122+
if (newImport.moduleSpecifier === undefined) {
123+
return [[], newImport.symbolName];
124+
}
119125

120126
// The trait might already be imported, possibly under a different name. If so, determine the
121127
// local name of the imported trait.

0 commit comments

Comments
 (0)