Skip to content

Commit

Permalink
Merge branch 'master' into fix/604
Browse files Browse the repository at this point in the history
  • Loading branch information
JamesHenry authored Aug 31, 2021
2 parents 4cd9233 + 3aa7f82 commit 1399bb9
Show file tree
Hide file tree
Showing 4 changed files with 80 additions and 65 deletions.
46 changes: 32 additions & 14 deletions packages/eslint-plugin-template/docs/rules/no-call-expression.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,39 +35,39 @@ The rule does not have any configuration options.
❌ - Examples of **incorrect** code for this rule:

```html
<div>{{ getInfo() }}</div>
~~~~~~~~~
<div>{{ getInfo()() }}</div>
~~~~~~~~~~~
```

```html
<a href="http://example.com">{{ getInfo().name }}</a>
~~~~~~~~~
<a href="{{ getUrls().user }}"></a>
~~~~~~~~~
```

```html
<a [href]="getUrl()">info</a>
~~~~~~~~
<p [test]="test?.getInfo()"></p>
~~~~~~~~~~~~~~~
```

```html
<a [href]="id && createUrl()">info</a>
~~~~~~~~~~~
<a [href]="id && createUrl() && test()($any)">info</a>
~~~~~~~~~~~ ~~~~~~~~~~~~
{{ id || obj?.nested1() }}
~~~~~~~~~~~~~~
```

```html
<a [href]="id ? a?.createUrl() : editUrl()">info</a>
~~~~~~~~~~~~~~ ~~~~~~~~~
{{ 1 === 2 ? 3 : obj?.nested1() }}
~~~~~~~~~~~~~~
<a [href]="id ? a?.createUrl() : editUrl(3)">info</a>
~~~~~~~~~~~~~~ ~~~~~~~~~~
{{ 1 === 2 ? 3 : obj?.nested1()() }}
~~~~~~~~~~~~~~~~
```

```html
{{ obj?.nested1() }} {{ obj!.nested1() }}
~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~
<button [type]="obj!.$any(b)!.getType()">info</button>
~~~~~~~~~~~~~~~~~~~~~~~
<button [type]="obj!.$any(b)!.getType()()">info</button>
~~~~~~~~~~~~~~~~~~~~~~~~~
<a [href]="obj.propertyA?.href()">info</a>
~~~~~~~~~~~~~~~~~~~~~
```
Expand All @@ -82,10 +82,28 @@ The rule does not have any configuration options.

```html
{{ info }}
```

```html
<button type="button" (click)="handleClick()">Click Here</button>
```

```html
{{ $any(info) }}
```

```html
<input (change)="obj?.changeHandler()">
```

```html
<form [formGroup]="form" (ngSubmit)="form.valid || save()"></form>
```

```html
<form [formGroup]="form" (ngSubmit)="form.valid && save()"></form>
```

```html
<form [formGroup]="form" (ngSubmit)="id ? save() : edit()"></form>
```
16 changes: 11 additions & 5 deletions packages/eslint-plugin-template/src/rules/no-call-expression.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
import type { MethodCall, SafeMethodCall } from '@angular/compiler';
import type {
FunctionCall,
MethodCall,
SafeMethodCall,
} from '@angular/compiler';
import { TmplAstBoundEvent } from '@angular/compiler';
import {
createESLintRule,
Expand Down Expand Up @@ -31,22 +35,24 @@ export default createESLintRule<Options, MessageIds>({
const sourceCode = context.getSourceCode();

return {
'MethodCall[name!="$any"], SafeMethodCall'(
node: MethodCall | SafeMethodCall,
'FunctionCall, MethodCall[name!="$any"], SafeMethodCall'(
node: FunctionCall | MethodCall | SafeMethodCall,
) {
const isChildOfBoundEvent = !!getNearestNodeFrom(node, isBoundEvent);
const isChildOfBoundEvent = Boolean(
getNearestNodeFrom(node, isBoundEvent),
);

if (isChildOfBoundEvent) return;

const {
sourceSpan: { start, end },
} = node;
context.report({
messageId: 'noCallExpression',
loc: {
start: sourceCode.getLocFromIndex(start),
end: sourceCode.getLocFromIndex(end),
},
messageId: 'noCallExpression',
});
},
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,63 +4,63 @@ import type { MessageIds } from '../../../src/rules/no-call-expression';
const messageId: MessageIds = 'noCallExpression';

export const valid = [
`
{{ info }}
<button type="button" (click)="handleClick()">Click Here</button>
{{ $any(info) }}
<input (change)="obj?.changeHandler()">
<form [formGroup]="form" (ngSubmit)="form.valid || save()"></form>
<form [formGroup]="form" (ngSubmit)="form.valid && save()"></form>
<form [formGroup]="form" (ngSubmit)="id ? save() : edit()"></form>
`,
'{{ info }}',
'<button type="button" (click)="handleClick()">Click Here</button>',
'{{ $any(info) }}',
'<input (change)="obj?.changeHandler()">',
'<form [formGroup]="form" (ngSubmit)="form.valid || save()"></form>',
'<form [formGroup]="form" (ngSubmit)="form.valid && save()"></form>',
'<form [formGroup]="form" (ngSubmit)="id ? save() : edit()"></form>',
];

export const invalid = [
convertAnnotatedSourceToFailureCase({
description: 'it should fail for call expression in an expression binding',
description: 'should fail for `FunctionCall` within `Interpolation`',
annotatedSource: `
<div>{{ getInfo() }}</div>
~~~~~~~~~
<div>{{ getInfo()() }}</div>
~~~~~~~~~~~
`,
messageId,
}),
convertAnnotatedSourceToFailureCase({
description:
'it should fail when using a property resulted from a call expression in an expression binding',
description: 'should fail for `MethodCall` within `TextAttribute`',
annotatedSource: `
<a href="http://example.com">{{ getInfo().name }}</a>
~~~~~~~~~
<a href="{{ getUrls().user }}"></a>
~~~~~~~~~
`,
messageId,
}),
convertAnnotatedSourceToFailureCase({
description: 'it should fail for call expression in a property binding',
description: 'should fail for `SafeMethodCall` within `BoundAttribute`',
annotatedSource: `
<a [href]="getUrl()">info</a>
~~~~~~~~
<p [test]="test?.getInfo()"></p>
~~~~~~~~~~~~~~~
`,
messageId,
}),
convertAnnotatedSourceToFailureCase({
description: 'it should fail for call expression with binaries',
description:
'should fail for `FunctionCall`, `MethodCall` and `SafeMethodCall` within `Binary`',
annotatedSource: `
<a [href]="id && createUrl()">info</a>
~~~~~~~~~~~
<a [href]="id && createUrl() && test()($any)">info</a>
~~~~~~~~~~~ ^^^^^^^^^^^^
{{ id || obj?.nested1() }}
^^^^^^^^^^^^^^
##############
`,
messages: [
{ char: '~', messageId },
{ char: '^', messageId },
{ char: '#', messageId },
],
}),
convertAnnotatedSourceToFailureCase({
description: 'it should fail for call expression within conditionals',
description:
'should fail for `FunctionCall`, `MethodCall` and `SafeMethodCall` within `Conditional`',
annotatedSource: `
<a [href]="id ? a?.createUrl() : editUrl()">info</a>
~~~~~~~~~~~~~~ ^^^^^^^^^
{{ 1 === 2 ? 3 : obj?.nested1() }}
##############
<a [href]="id ? a?.createUrl() : editUrl(3)">info</a>
~~~~~~~~~~~~~~ ^^^^^^^^^^
{{ 1 === 2 ? 3 : obj?.nested1()() }}
################
`,
messages: [
{ char: '~', messageId },
Expand All @@ -69,12 +69,12 @@ export const invalid = [
],
}),
convertAnnotatedSourceToFailureCase({
description: 'it should fail for safe/unsafe method calls',
description: 'should fail for safe/unsafe calls',
annotatedSource: `
{{ obj?.nested1() }} {{ obj!.nested1() }}
~~~~~~~~~~~~~~ ^^^^^^^^^^^^^^
<button [type]="obj!.$any(b)!.getType()">info</button>
#######################
<button [type]="obj!.$any(b)!.getType()()">info</button>
#########################
<a [href]="obj.propertyA?.href()">info</a>
%%%%%%%%%%%%%%%%%%%%%
`,
Expand Down
21 changes: 6 additions & 15 deletions packages/utils/src/test-helpers.ts
Original file line number Diff line number Diff line change
@@ -1,32 +1,22 @@
import type { TSESLint } from '@typescript-eslint/experimental-utils';

/**
* FROM CODELYZER
*/
interface SourcePosition {
// #region FROM CODELYZER
type SourcePosition = {
readonly character: number;
readonly line: number;
}
};

/**
* FROM CODELYZER
*/
interface ExpectedFailure {
type ExpectedFailure = {
readonly endPosition?: SourcePosition;
readonly message: string;
readonly startPosition?: SourcePosition;
}
};

/**
* FROM CODELYZER
*/
function escapeRegexp(value: string) {
return value.replace(/[-/\\^$*+?.()|[\]{}]/g, '\\$&');
}

/**
* FROM CODELYZER
*
* When testing a failure, we also test to see if the linter will report the correct place where
* the source code doesn't match the rule.
*
Expand Down Expand Up @@ -116,6 +106,7 @@ export function parseInvalidSource(
source: newSource,
};
}
// #endregion FROM CODELYZER

type BaseErrorOptions = {
readonly description: string;
Expand Down

0 comments on commit 1399bb9

Please sign in to comment.