Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit d21c89b

Browse filesBrowse files
committed
refactor(compiler): account for pipes without names
Updates the compiler logic to account for pipe definitions that may not have names.
1 parent 26ad66c commit d21c89b
Copy full SHA for d21c89b

File tree

17 files changed

+234
-74
lines changed
Filter options

17 files changed

+234
-74
lines changed

‎packages/compiler-cli/src/ngtsc/annotations/component/src/handler.ts

Copy file name to clipboardExpand all lines: packages/compiler-cli/src/ngtsc/annotations/component/src/handler.ts
+2-2Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1675,7 +1675,7 @@ export class ComponentDecoratorHandler
16751675
const scopeDeps = isModuleScope ? scope.compilation.dependencies : scope.dependencies;
16761676
for (const dep of scopeDeps) {
16771677
// Outside of selectorless the pipes are referred to by their defined name.
1678-
if (dep.kind === MetaKind.Pipe) {
1678+
if (dep.kind === MetaKind.Pipe && dep.name !== null) {
16791679
pipes.set(dep.name, dep);
16801680
}
16811681
dependencies.push(dep);
@@ -1723,7 +1723,7 @@ export class ComponentDecoratorHandler
17231723

17241724
const deferBlockMatcher = new SelectorMatcher<DirectiveMeta[]>();
17251725
for (const dep of allDependencies) {
1726-
if (dep.kind === MetaKind.Pipe) {
1726+
if (dep.kind === MetaKind.Pipe && dep.name !== null) {
17271727
pipes.set(dep.name, dep);
17281728
} else if (dep.kind === MetaKind.Directive && dep.selector !== null) {
17291729
deferBlockMatcher.addSelectables(CssSelector.parse(dep.selector), [dep]);

‎packages/compiler-cli/src/ngtsc/annotations/src/pipe.ts

Copy file name to clipboardExpand all lines: packages/compiler-cli/src/ngtsc/annotations/src/pipe.ts
+54-51Lines changed: 54 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@ import {
1414
FactoryTarget,
1515
R3ClassMetadata,
1616
R3PipeMetadata,
17-
WrappedNodeExpr,
1817
} from '@angular/compiler';
1918
import ts from 'typescript';
2019

@@ -53,7 +52,7 @@ import {
5352
export interface PipeHandlerData {
5453
meta: R3PipeMetadata;
5554
classMetadata: R3ClassMetadata | null;
56-
pipeNameExpr: ts.Expression;
55+
pipeNameExpr: ts.Expression | null;
5756
decorator: ts.Decorator | null;
5857
}
5958

@@ -137,62 +136,66 @@ export class PipeDecoratorHandler
137136
`@Pipe must be called`,
138137
);
139138
}
140-
if (decorator.args.length !== 1) {
141-
throw new FatalDiagnosticError(
142-
ErrorCode.DECORATOR_ARITY_WRONG,
143-
decorator.node,
144-
'@Pipe must have exactly one argument',
145-
);
146-
}
147-
const meta = unwrapExpression(decorator.args[0]);
148-
if (!ts.isObjectLiteralExpression(meta)) {
149-
throw new FatalDiagnosticError(
150-
ErrorCode.DECORATOR_ARG_NOT_LITERAL,
151-
meta,
152-
'@Pipe must have a literal argument',
153-
);
154-
}
155-
const pipe = reflectObjectLiteral(meta);
156-
157-
if (!pipe.has('name')) {
158-
throw new FatalDiagnosticError(
159-
ErrorCode.PIPE_MISSING_NAME,
160-
meta,
161-
`@Pipe decorator is missing name field`,
162-
);
163-
}
164-
const pipeNameExpr = pipe.get('name')!;
165-
const pipeName = this.evaluator.evaluate(pipeNameExpr);
166-
if (typeof pipeName !== 'string') {
167-
throw createValueHasWrongTypeError(pipeNameExpr, pipeName, `@Pipe.name must be a string`);
168-
}
169139

140+
const meta = decorator.args.length === 0 ? null : unwrapExpression(decorator.args[0]);
141+
let pipeName: string | null = null;
142+
let pipeNameExpr: ts.Expression | null = null;
170143
let pure = true;
171-
if (pipe.has('pure')) {
172-
const expr = pipe.get('pure')!;
173-
const pureValue = this.evaluator.evaluate(expr);
174-
if (typeof pureValue !== 'boolean') {
175-
throw createValueHasWrongTypeError(expr, pureValue, `@Pipe.pure must be a boolean`);
176-
}
177-
pure = pureValue;
178-
}
179-
180144
let isStandalone = this.implicitStandaloneValue;
181-
if (pipe.has('standalone')) {
182-
const expr = pipe.get('standalone')!;
183-
const resolved = this.evaluator.evaluate(expr);
184-
if (typeof resolved !== 'boolean') {
185-
throw createValueHasWrongTypeError(expr, resolved, `standalone flag must be a boolean`);
145+
146+
if (meta !== null) {
147+
if (!ts.isObjectLiteralExpression(meta)) {
148+
throw new FatalDiagnosticError(
149+
ErrorCode.DECORATOR_ARG_NOT_LITERAL,
150+
meta,
151+
'@Pipe must have a literal argument',
152+
);
186153
}
187-
isStandalone = resolved;
188154

189-
if (!isStandalone && this.strictStandalone) {
155+
const pipe = reflectObjectLiteral(meta);
156+
if (!pipe.has('name')) {
190157
throw new FatalDiagnosticError(
191-
ErrorCode.NON_STANDALONE_NOT_ALLOWED,
192-
expr,
193-
`Only standalone pipes are allowed when 'strictStandalone' is enabled.`,
158+
ErrorCode.PIPE_MISSING_NAME,
159+
meta,
160+
`@Pipe decorator is missing name field`,
161+
);
162+
}
163+
pipeNameExpr = pipe.get('name')!;
164+
const evaluatedName = this.evaluator.evaluate(pipeNameExpr);
165+
if (typeof evaluatedName !== 'string') {
166+
throw createValueHasWrongTypeError(
167+
pipeNameExpr,
168+
evaluatedName,
169+
`@Pipe.name must be a string`,
194170
);
195171
}
172+
pipeName = evaluatedName;
173+
174+
if (pipe.has('pure')) {
175+
const expr = pipe.get('pure')!;
176+
const pureValue = this.evaluator.evaluate(expr);
177+
if (typeof pureValue !== 'boolean') {
178+
throw createValueHasWrongTypeError(expr, pureValue, `@Pipe.pure must be a boolean`);
179+
}
180+
pure = pureValue;
181+
}
182+
183+
if (pipe.has('standalone')) {
184+
const expr = pipe.get('standalone')!;
185+
const resolved = this.evaluator.evaluate(expr);
186+
if (typeof resolved !== 'boolean') {
187+
throw createValueHasWrongTypeError(expr, resolved, `standalone flag must be a boolean`);
188+
}
189+
isStandalone = resolved;
190+
191+
if (!isStandalone && this.strictStandalone) {
192+
throw new FatalDiagnosticError(
193+
ErrorCode.NON_STANDALONE_NOT_ALLOWED,
194+
expr,
195+
`Only standalone pipes are allowed when 'strictStandalone' is enabled.`,
196+
);
197+
}
198+
}
196199
}
197200

198201
return {
@@ -216,7 +219,7 @@ export class PipeDecoratorHandler
216219
}
217220

218221
symbol(node: ClassDeclaration, analysis: Readonly<PipeHandlerData>): PipeSymbol {
219-
return new PipeSymbol(node, analysis.meta.pipeName);
222+
return new PipeSymbol(node, analysis.meta.pipeName ?? analysis.meta.name);
220223
}
221224

222225
register(node: ClassDeclaration, analysis: Readonly<PipeHandlerData>): void {

‎packages/compiler-cli/src/ngtsc/docs/src/entities.ts

Copy file name to clipboardExpand all lines: packages/compiler-cli/src/ngtsc/docs/src/entities.ts
+1-1Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,7 @@ export interface DirectiveEntry extends ClassEntry {
137137
}
138138

139139
export interface PipeEntry extends ClassEntry {
140-
pipeName: string;
140+
pipeName: string | null;
141141
isStandalone: boolean;
142142
usage: string;
143143
isPure: boolean;

‎packages/compiler-cli/src/ngtsc/metadata/src/api.ts

Copy file name to clipboardExpand all lines: packages/compiler-cli/src/ngtsc/metadata/src/api.ts
+1-1Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -358,7 +358,7 @@ export interface TemplateGuardMeta {
358358
export interface PipeMeta {
359359
kind: MetaKind.Pipe;
360360
ref: Reference<ClassDeclaration>;
361-
name: string;
361+
name: string | null;
362362
nameExpr: ts.Expression | null;
363363
isStandalone: boolean;
364364
isPure: boolean;

‎packages/compiler-cli/src/ngtsc/metadata/src/dts.ts

Copy file name to clipboardExpand all lines: packages/compiler-cli/src/ngtsc/metadata/src/dts.ts
+6-2Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -246,11 +246,15 @@ export class DtsMetadataReader implements MetadataReader {
246246
return null;
247247
}
248248
const type = def.type.typeArguments[1];
249-
if (!ts.isLiteralTypeNode(type) || !ts.isStringLiteral(type.literal)) {
249+
250+
if (
251+
!ts.isLiteralTypeNode(type) ||
252+
(!ts.isStringLiteral(type.literal) && type.literal.kind !== ts.SyntaxKind.NullKeyword)
253+
) {
250254
// The type metadata was the wrong type.
251255
return null;
252256
}
253-
const name = type.literal.text;
257+
const name = ts.isStringLiteral(type.literal) ? type.literal.text : null;
254258

255259
const isStandalone =
256260
def.type.typeArguments.length > 2 && (readBooleanType(def.type.typeArguments[2]) ?? false);

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

Copy file name to clipboardExpand all lines: packages/compiler-cli/src/ngtsc/scope/src/typecheck.ts
+1-1Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,7 @@ export class TypeCheckScopeRegistry {
138138
for (const dep of allDependencies) {
139139
if (dep.kind === MetaKind.Directive) {
140140
directives.push(dep);
141-
} else if (dep.kind === MetaKind.Pipe) {
141+
} else if (dep.kind === MetaKind.Pipe && dep.name !== null) {
142142
pipes.set(dep.name, dep);
143143
}
144144
}

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

Copy file name to clipboardExpand all lines: packages/compiler-cli/src/ngtsc/typecheck/api/scope.ts
+1-1Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ export interface PotentialPipe {
8383
/**
8484
* Name of the pipe.
8585
*/
86-
name: string;
86+
name: string | null;
8787

8888
/**
8989
* Whether or not this pipe is in scope.

‎packages/compiler-cli/src/ngtsc/validation/src/rules/unused_standalone_imports_rule.ts

Copy file name to clipboardExpand all lines: packages/compiler-cli/src/ngtsc/validation/src/rules/unused_standalone_imports_rule.ts
+1Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,7 @@ export class UnusedStandaloneImportsRule implements SourceFileValidatorRule {
138138
if (
139139
pipeMeta !== null &&
140140
pipeMeta.isStandalone &&
141+
pipeMeta.name !== null &&
141142
!usedPipes.has(pipeMeta.name) &&
142143
!this.isPotentialSharedReference(current, rawImports)
143144
) {

‎packages/compiler-cli/test/compliance/test_cases/r3_compiler_compliance/components_and_directives/pipes/GOLDEN_PARTIAL.js

Copy file name to clipboardExpand all lines: packages/compiler-cli/test/compliance/test_cases/r3_compiler_compliance/components_and_directives/pipes/GOLDEN_PARTIAL.js
+27Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -243,3 +243,30 @@ export declare class MyModule {
243243
static ɵinj: i0.ɵɵInjectorDeclaration<MyModule>;
244244
}
245245

246+
/****************************************************************************************************
247+
* PARTIAL FILE: nameless_pipe.js
248+
****************************************************************************************************/
249+
import { Pipe } from '@angular/core';
250+
import * as i0 from "@angular/core";
251+
export class PipeWithoutName {
252+
transform(value) {
253+
return value;
254+
}
255+
}
256+
PipeWithoutName.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: PipeWithoutName, deps: [], target: i0.ɵɵFactoryTarget.Pipe });
257+
PipeWithoutName.ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: PipeWithoutName, isStandalone: true, name: "PipeWithoutName" });
258+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: PipeWithoutName, decorators: [{
259+
type: Pipe
260+
}] });
261+
262+
/****************************************************************************************************
263+
* PARTIAL FILE: nameless_pipe.d.ts
264+
****************************************************************************************************/
265+
import { PipeTransform } from '@angular/core';
266+
import * as i0 from "@angular/core";
267+
export declare class PipeWithoutName implements PipeTransform {
268+
transform(value: unknown): unknown;
269+
static ɵfac: i0.ɵɵFactoryDeclaration<PipeWithoutName, never>;
270+
static ɵpipe: i0.ɵɵPipeDeclaration<PipeWithoutName, null, true>;
271+
}
272+

‎packages/compiler-cli/test/compliance/test_cases/r3_compiler_compliance/components_and_directives/pipes/TEST_CASES.json

Copy file name to clipboardExpand all lines: packages/compiler-cli/test/compliance/test_cases/r3_compiler_compliance/components_and_directives/pipes/TEST_CASES.json
+14Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,20 @@
111111
]
112112
}
113113
]
114+
},
115+
{
116+
"description": "should handle a pipe that does not have a name",
117+
"inputFiles": [
118+
"nameless_pipe.ts"
119+
],
120+
"expectations": [
121+
{
122+
"failureMessage": "Invalid definition",
123+
"files": [
124+
"nameless_pipe.js"
125+
]
126+
}
127+
]
114128
}
115129
]
116130
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
ɵɵdefinePipe({name: "PipeWithoutName", type: PipeWithoutName, pure: true});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import {Pipe, PipeTransform} from '@angular/core';
2+
3+
@Pipe()
4+
export class PipeWithoutName implements PipeTransform {
5+
transform(value: unknown) {
6+
return value;
7+
}
8+
}

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.