Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 2ffe2ea

Browse files
rafaelss95mgechev
authored andcommitted
fix(templates-no-negated-async): not reporting failures for some cases (#694)
1 parent 5a84041 commit 2ffe2ea

File tree

2 files changed

+152
-144
lines changed

2 files changed

+152
-144
lines changed

src/templatesNoNegatedAsyncRule.ts

+47-52
Original file line numberDiff line numberDiff line change
@@ -1,78 +1,73 @@
1-
import * as Lint from 'tslint';
2-
import * as ts from 'typescript';
1+
import { IRuleMetadata, RuleFailure, Rules } from 'tslint';
2+
import { SourceFile } from 'typescript';
33
import { NgWalker } from './angular/ngWalker';
44
import { RecursiveAngularExpressionVisitor } from './angular/templates/recursiveAngularExpressionVisitor';
5-
import * as e from '@angular/compiler/src/expression_parser/ast';
6-
import * as ast from '@angular/compiler';
5+
import { Binary, PrefixNot } from '@angular/compiler/src/expression_parser/ast';
6+
import { AST, BindingPipe, LiteralPrimitive } from '@angular/compiler';
77

88
const unstrictEqualityOperator = '==';
99

10-
class TemplateToNgTemplateVisitor extends RecursiveAngularExpressionVisitor {
11-
visitBinary(expr: e.Binary, context: any): any {
12-
if (!this.isAsyncBinding(expr.left)) {
13-
return super.visitBinary(expr, context);
14-
}
15-
if (!(expr.right instanceof ast.LiteralPrimitive) || expr.right.value !== false || expr.operation !== unstrictEqualityOperator) {
16-
return super.visitBinary(expr, context);
17-
}
10+
const isAsyncBinding = (ast: AST): boolean => {
11+
return ast instanceof BindingPipe && ast.name === 'async';
12+
};
1813

19-
const {
20-
left: {
21-
span: { end: endLeftSpan }
22-
},
23-
right: {
24-
span: { start: startRightSpan }
25-
},
26-
span: { end: spanEnd, start: spanStart }
27-
} = expr;
28-
const operator = this.codeWithMap.code.slice(endLeftSpan, startRightSpan);
29-
const operatorStart = /^.*==/.exec(operator)![0].length - unstrictEqualityOperator.length;
14+
class TemplateToNgTemplateVisitor extends RecursiveAngularExpressionVisitor {
15+
visitBinary(ast: Binary, context: any): any {
16+
this.validateBinary(ast);
17+
super.visitBinary(ast, context);
18+
}
3019

31-
this.addFailureFromStartToEnd(spanStart, spanEnd, 'Async pipes must use strict equality `===` when comparing with `false`', [
32-
new Lint.Replacement(this.getSourcePosition(endLeftSpan) + operatorStart, unstrictEqualityOperator.length, '===')
33-
]);
34-
super.visitBinary(expr, context);
20+
visitPrefixNot(ast: PrefixNot, context: any): any {
21+
this.validatePrefixNot(ast);
22+
super.visitPrefixNot(ast, context);
3523
}
3624

37-
visitPrefixNot(expr: e.PrefixNot, context: any): any {
38-
if (!this.isAsyncBinding(expr.expression)) {
39-
return super.visitPrefixNot(expr, context);
25+
private validateBinary(ast: Binary): void {
26+
const { left, operation, right } = ast;
27+
28+
if (!isAsyncBinding(left) || !(right instanceof LiteralPrimitive) || right.value !== false || operation !== unstrictEqualityOperator) {
29+
return;
4030
}
41-
const {
42-
span: { end: spanEnd, start: spanStart }
43-
} = expr;
44-
const absoluteStart = this.getSourcePosition(spanStart);
4531

46-
// Angular includes the whitespace after an expression, we want to trim that
47-
const expressionSource = this.codeWithMap.code.slice(spanStart, spanEnd);
48-
const concreteWidth = spanEnd - spanStart - / *$/.exec(expressionSource)![0].length;
32+
this.generateFailure(ast, Rule.FAILURE_STRING_UNSTRICT_EQUALITY);
33+
}
34+
35+
private validatePrefixNot(ast: PrefixNot): void {
36+
const { expression } = ast;
37+
38+
if (!isAsyncBinding(expression)) {
39+
return;
40+
}
4941

50-
this.addFailureFromStartToEnd(spanStart, spanEnd, 'Async pipes can not be negated, use (observable | async) === false instead', [
51-
new Lint.Replacement(absoluteStart + concreteWidth, 1, ' === false '),
52-
new Lint.Replacement(absoluteStart, 1, '')
53-
]);
42+
this.generateFailure(ast, Rule.FAILURE_STRING_NEGATED_PIPE);
5443
}
5544

56-
protected isAsyncBinding(expr: ast.AST) {
57-
return expr instanceof ast.BindingPipe && expr.name === 'async';
45+
private generateFailure(ast: Binary | PrefixNot, errorMessage: string): void {
46+
const {
47+
span: { end: spanEnd, start: spanStart }
48+
} = ast;
49+
50+
this.addFailureFromStartToEnd(spanStart, spanEnd, errorMessage);
5851
}
5952
}
6053

61-
export class Rule extends Lint.Rules.AbstractRule {
62-
public static metadata: Lint.IRuleMetadata = {
63-
ruleName: 'templates-no-negated-async',
64-
type: 'functionality',
54+
export class Rule extends Rules.AbstractRule {
55+
static readonly metadata: IRuleMetadata = {
6556
description: 'Ensures that strict equality is used when evaluating negations on async pipe output.',
57+
options: null,
58+
optionsDescription: 'Not configurable.',
6659
rationale:
6760
'Async pipe evaluate to `null` before the observable or promise emits, which can lead to layout thrashing as' +
6861
' components load. Prefer strict `=== false` checks instead.',
69-
options: null,
70-
optionsDescription: 'Not configurable.',
71-
typescriptOnly: true,
72-
hasFix: true
62+
ruleName: 'templates-no-negated-async',
63+
type: 'functionality',
64+
typescriptOnly: true
7365
};
7466

75-
public apply(sourceFile: ts.SourceFile): Lint.RuleFailure[] {
67+
static readonly FAILURE_STRING_NEGATED_PIPE = 'Async pipes can not be negated, use (observable | async) === false instead';
68+
static readonly FAILURE_STRING_UNSTRICT_EQUALITY = 'Async pipes must use strict equality `===` when comparing with `false`';
69+
70+
apply(sourceFile: SourceFile): RuleFailure[] {
7671
return this.applyWithWalker(
7772
new NgWalker(sourceFile, this.getOptions(), {
7873
expressionVisitorCtrl: TemplateToNgTemplateVisitor

0 commit comments

Comments
 (0)