forked from angular/angular
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(compiler-cli): detect missing structural directive imports (angu…
…lar#59443) Adds a new diagnostic that ensures that a standalone component using custom structural directives in a template has the necessary imports for those directives. Fixes angular#37322 PR Close angular#59443
- Loading branch information
1 parent
3b3040d
commit ed705a8
Showing
12 changed files
with
429 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,61 @@ | ||
# Missing structural directive | ||
|
||
This diagnostic ensures that a standalone component using custom structural directives (e.g., `*select` or `*featureFlag`) in a template has the necessary imports for those directives. | ||
|
||
<docs-code language="typescript"> | ||
|
||
import {Component} from '@angular/core'; | ||
|
||
@Component({ | ||
// Template uses `*select`, but no corresponding directive imported. | ||
imports: [], | ||
template: `<p *select="let data from source">{{data}}</p>`, | ||
}) | ||
class MyComponent {} | ||
|
||
</docs-code> | ||
|
||
## What's wrong with that? | ||
|
||
Using a structural directive without importing it will fail at runtime, as Angular attempts to bind to a `select` property of the HTML element, which does not exist. | ||
|
||
## What should I do instead? | ||
|
||
Make sure that the corresponding structural directive is imported into the component: | ||
|
||
<docs-code language="typescript"> | ||
|
||
import {Component} from '@angular/core'; | ||
import {SelectDirective} from 'my-directives'; | ||
|
||
@Component({ | ||
// Add `SelectDirective` to the `imports` array to make it available in the template. | ||
imports: [SelectDirective], | ||
template: `<p *select="let data from source">{{data}}</p>`, | ||
}) | ||
class MyComponent {} | ||
|
||
</docs-code> | ||
|
||
## Configuration requirements | ||
|
||
[`strictTemplates`](tools/cli/template-typecheck#strict-mode) must be enabled for any extended diagnostic to emit. | ||
`missingStructuralDirective` has no additional requirements beyond `strictTemplates`. | ||
|
||
## What if I can't avoid this? | ||
|
||
This diagnostic can be disabled by editing the project's `tsconfig.json` file: | ||
|
||
<docs-code language="json"> | ||
{ | ||
"angularCompilerOptions": { | ||
"extendedDiagnostics": { | ||
"checks": { | ||
"missingStructuralDirective": "suppress" | ||
} | ||
} | ||
} | ||
} | ||
</docs-code> | ||
|
||
See [extended diagnostic configuration](extended-diagnostics#configuration) for more info. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
15 changes: 15 additions & 0 deletions
15
...compiler-cli/src/ngtsc/typecheck/extended/checks/missing_structural_directive/BUILD.bazel
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
load("//tools:defaults.bzl", "ts_library") | ||
|
||
ts_library( | ||
name = "missing_structural_directive", | ||
srcs = ["index.ts"], | ||
visibility = ["//packages/compiler-cli/src/ngtsc:__subpackages__"], | ||
deps = [ | ||
"//packages/compiler", | ||
"//packages/compiler-cli/src/ngtsc/core:api", | ||
"//packages/compiler-cli/src/ngtsc/diagnostics", | ||
"//packages/compiler-cli/src/ngtsc/typecheck/api", | ||
"//packages/compiler-cli/src/ngtsc/typecheck/extended/api", | ||
"@npm//typescript", | ||
], | ||
) |
91 changes: 91 additions & 0 deletions
91
...es/compiler-cli/src/ngtsc/typecheck/extended/checks/missing_structural_directive/index.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,91 @@ | ||
/** | ||
* @license | ||
* Copyright Google LLC All Rights Reserved. | ||
* | ||
* Use of this source code is governed by an MIT-style license that can be | ||
* found in the LICENSE file at https://angular.dev/license | ||
*/ | ||
|
||
import {AST, TmplAstNode, TmplAstTemplate} from '@angular/compiler'; | ||
import ts from 'typescript'; | ||
|
||
import {NgCompilerOptions} from '../../../../core/api'; | ||
import {ErrorCode, ExtendedTemplateDiagnosticName} from '../../../../diagnostics'; | ||
import {NgTemplateDiagnostic} from '../../../api'; | ||
import {TemplateCheckFactory, TemplateCheckWithVisitor, TemplateContext} from '../../api'; | ||
|
||
/** | ||
* The list of known control flow directives present in the `CommonModule`. | ||
* | ||
* If these control flow directives are missing they will be reported by a separate diagnostic. | ||
*/ | ||
export const KNOWN_CONTROL_FLOW_DIRECTIVES = new Set([ | ||
'ngIf', | ||
'ngFor', | ||
'ngSwitch', | ||
'ngSwitchCase', | ||
'ngSwitchDefault', | ||
]); | ||
|
||
/** | ||
* Ensures that there are no structural directives (something like *select or *featureFlag) | ||
* used in a template of a *standalone* component without importing the directive. Returns | ||
* diagnostics in case such a directive is detected. | ||
* | ||
* Note: this check only handles the cases when structural directive syntax is used (e.g. `*featureFlag`). | ||
* Regular binding syntax (e.g. `[featureFlag]`) is handled separately in type checker and treated as a | ||
* hard error instead of a warning. | ||
*/ | ||
class MissingStructuralDirectiveCheck extends TemplateCheckWithVisitor<ErrorCode.MISSING_STRUCTURAL_DIRECTIVE> { | ||
override code = ErrorCode.MISSING_STRUCTURAL_DIRECTIVE as const; | ||
|
||
override run( | ||
ctx: TemplateContext<ErrorCode.MISSING_STRUCTURAL_DIRECTIVE>, | ||
component: ts.ClassDeclaration, | ||
template: TmplAstNode[], | ||
) { | ||
const componentMetadata = ctx.templateTypeChecker.getDirectiveMetadata(component); | ||
// Avoid running this check for non-standalone components. | ||
if (!componentMetadata || !componentMetadata.isStandalone) { | ||
return []; | ||
} | ||
return super.run(ctx, component, template); | ||
} | ||
|
||
override visitNode( | ||
ctx: TemplateContext<ErrorCode.MISSING_STRUCTURAL_DIRECTIVE>, | ||
component: ts.ClassDeclaration, | ||
node: TmplAstNode | AST, | ||
): NgTemplateDiagnostic<ErrorCode.MISSING_STRUCTURAL_DIRECTIVE>[] { | ||
if (!(node instanceof TmplAstTemplate)) return []; | ||
|
||
const customStructuralDirective = node.templateAttrs.find( | ||
(attr) => !KNOWN_CONTROL_FLOW_DIRECTIVES.has(attr.name), | ||
); | ||
if (!customStructuralDirective) return []; | ||
|
||
const symbol = ctx.templateTypeChecker.getSymbolOfNode(node, component); | ||
if (symbol === null || symbol.directives.length > 0) { | ||
return []; | ||
} | ||
|
||
const sourceSpan = customStructuralDirective.keySpan || customStructuralDirective.sourceSpan; | ||
const errorMessage = | ||
`An unknown structural directive \`${customStructuralDirective.name}\` was used in the template, ` + | ||
`without a corresponding import in the component. ` + | ||
`Make sure that the directive is included in the \`@Component.imports\` array of this component.`; | ||
const diagnostic = ctx.makeTemplateDiagnostic(sourceSpan, errorMessage); | ||
return [diagnostic]; | ||
} | ||
} | ||
|
||
export const factory: TemplateCheckFactory< | ||
ErrorCode.MISSING_STRUCTURAL_DIRECTIVE, | ||
ExtendedTemplateDiagnosticName.MISSING_STRUCTURAL_DIRECTIVE | ||
> = { | ||
code: ErrorCode.MISSING_STRUCTURAL_DIRECTIVE, | ||
name: ExtendedTemplateDiagnosticName.MISSING_STRUCTURAL_DIRECTIVE, | ||
create: (options: NgCompilerOptions) => { | ||
return new MissingStructuralDirectiveCheck(); | ||
}, | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
30 changes: 30 additions & 0 deletions
30
...ler-cli/src/ngtsc/typecheck/extended/test/checks/missing_structural_directive/BUILD.bazel
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
load("//tools:defaults.bzl", "jasmine_node_test", "ts_library") | ||
|
||
ts_library( | ||
name = "test_lib", | ||
testonly = True, | ||
srcs = ["missing_structural_directive_spec.ts"], | ||
deps = [ | ||
"//packages/compiler", | ||
"//packages/compiler-cli/src/ngtsc/core:api", | ||
"//packages/compiler-cli/src/ngtsc/diagnostics", | ||
"//packages/compiler-cli/src/ngtsc/file_system", | ||
"//packages/compiler-cli/src/ngtsc/file_system/testing", | ||
"//packages/compiler-cli/src/ngtsc/testing", | ||
"//packages/compiler-cli/src/ngtsc/typecheck/extended", | ||
"//packages/compiler-cli/src/ngtsc/typecheck/extended/checks/missing_structural_directive", | ||
"//packages/compiler-cli/src/ngtsc/typecheck/testing", | ||
"@npm//typescript", | ||
], | ||
) | ||
|
||
jasmine_node_test( | ||
name = "test", | ||
bootstrap = ["//tools/testing:node_no_angular"], | ||
data = [ | ||
"//packages/core:npm_package", | ||
], | ||
deps = [ | ||
":test_lib", | ||
], | ||
) |
Oops, something went wrong.