Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions baselines/packages/mimir/api/packlist.txt
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,9 @@ src/rules/prefer-number-methods.js.map
src/rules/prefer-object-spread.d.ts
src/rules/prefer-object-spread.js
src/rules/prefer-object-spread.js.map
src/rules/prefer-spread-arguments.d.ts
src/rules/prefer-spread-arguments.js
src/rules/prefer-spread-arguments.js.map
src/rules/return-never-call.d.ts
src/rules/return-never-call.js
src/rules/return-never-call.js.map
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import { TypedRule } from '@fimbul/ymir';
export declare class Rule extends TypedRule {
apply(): void;
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,14 @@
interface Empty {}
class Foo {
dontSpreadTypeParam<T extends object>(param: T, param2: T | undefined) {
spreadTypeParam<T extends object, U>(param: T, param2: T | undefined, param3: U) {
({...param});
({...param2});
({...param3});
}
dontSpreadPrimitiveTypeParameter<T extends number, U extends object | V, V extends boolean>(param: T, param2: U, param3: V) {
Object.assign({}, param);
Object.assign({}, param2);
Object.assign({}, param3);
}
dontSpreadNonObject(param: number | boolean) {
return Object.assign({}, param);
Expand All @@ -27,7 +33,13 @@ class Foo {
spreadAny(param: any) {
return {...param};
}
dontSpreadThis() {
spreadThis() {
return {...this};
}
spreadThisParameter(this: object) {
return {...this};
}
dontSpreadPrimitiveThis(this: number) {
return Object.assign({}, this);
}
spreadUnknown(param: unknown) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,17 @@
interface Empty {}
class Foo {
dontSpreadTypeParam<T extends object>(param: T, param2: T | undefined) {
spreadTypeParam<T extends object, U>(param: T, param2: T | undefined, param3: U) {
Object.assign({}, param);
~~~~~~~~~~~~~~~~~~~~~~~~ [error prefer-object-spread: Prefer object spread over 'Object.assign'.]
Object.assign({}, param2);
~~~~~~~~~~~~~~~~~~~~~~~~~ [error prefer-object-spread: Prefer object spread over 'Object.assign'.]
Object.assign({}, param3);
~~~~~~~~~~~~~~~~~~~~~~~~~ [error prefer-object-spread: Prefer object spread over 'Object.assign'.]
}
dontSpreadPrimitiveTypeParameter<T extends number, U extends object | V, V extends boolean>(param: T, param2: U, param3: V) {
Object.assign({}, param);
Object.assign({}, param2);
Object.assign({}, param3);
}
dontSpreadNonObject(param: number | boolean) {
return Object.assign({}, param);
Expand Down Expand Up @@ -31,7 +40,15 @@ class Foo {
return Object.assign({}, param);
~~~~~~~~~~~~~~~~~~~~~~~~ [error prefer-object-spread: Prefer object spread over 'Object.assign'.]
}
dontSpreadThis() {
spreadThis() {
return Object.assign({}, this);
~~~~~~~~~~~~~~~~~~~~~~~ [error prefer-object-spread: Prefer object spread over 'Object.assign'.]
}
spreadThisParameter(this: object) {
return Object.assign({}, this);
~~~~~~~~~~~~~~~~~~~~~~~ [error prefer-object-spread: Prefer object spread over 'Object.assign'.]
}
dontSpreadPrimitiveThis(this: number) {
return Object.assign({}, this);
}
spreadUnknown(param: unknown) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
export {};

declare const args: any[];

alert(...args);
alert(...args);
alert.apply(alert, args);
alert.apply(null, ...[args] as const);
alert.apply(...[null, args] as const);

declare const fn: ((...args: any[]) => void) | undefined;
fn!(...args);
fn?.(...args);
(fn as (...args: any[]) => void)(...args);

declare const obj: {apply(a: any, b: any): any};
obj.apply(null, args);

const nested = {obj};
nested.obj.apply(nested, args);

console.log.apply(null, args);
console.log(...args);
console.log.apply(console.log, args);
console.log.apply(obj, args);
console['log'](...args);
(console['log'])!(...args);
window.console.log(...args);
window.console.log(...args);
window.console.log(...args);
window['console'].log(...args);
window['console'].log(...args);

declare let objArr: {fn: (...args: any[]) => void}[];
declare let i: number;
objArr[0].fn(...args);
objArr[0].fn(...args);
objArr[0].fn.apply(objArr[1], args);
objArr[i].fn(...args);
objArr[objArr.length].fn(...args);
objArr[objArr.length].fn(...args);
objArr[objArr.length - 1].fn.apply(objArr[objArr.length - 1], args);

objArr[Symbol.iterator](...args);

class C {
#fn!: (...args: any[]) => void;
#obj: {fn: (...args: any[]) => void};
[key: string]: {fn: (...args: any[]) => void};
constructor() {
this.#fn(...args);
this.#fn.apply(new C(), args);
this.#fn.apply(null, args);
this.#obj.fn(...args);
this.#obj.fn.apply(this, args);

this.#obj.fn.apply(this['#obj'], args);
this.#obj.fn.apply(this['obj'], args);
this['#obj'].fn.apply(this.#obj, args);
this['#obj'].fn(...args);
}
}

class Base {
fn(...args: any[]) {};
}

class Derived extends Base {
fn() {
super.fn(...args);
}
}

fn(...args);
fn(...args);

fn(...args);

fn.apply //
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
export {};

declare const args: any[];

alert.apply(null, args);
~~~~~~~~~~~~~~~~~~~~~~~ [error prefer-spread-arguments: Prefer spread arguments over 'Function.prototype.apply'.]
alert.apply(undefined, args);
~~~~~~~~~~~~~~~~~~~~~~~~~~~~ [error prefer-spread-arguments: Prefer spread arguments over 'Function.prototype.apply'.]
alert.apply(alert, args);
alert.apply(null, ...[args] as const);
alert.apply(...[null, args] as const);

declare const fn: ((...args: any[]) => void) | undefined;
fn!.apply(null, args);
~~~~~~~~~~~~~~~~~~~~~ [error prefer-spread-arguments: Prefer spread arguments over 'Function.prototype.apply'.]
fn?.apply(null, args);
~~~~~~~~~~~~~~~~~~~~~ [error prefer-spread-arguments: Prefer spread arguments over 'Function.prototype.apply'.]
(fn as (...args: any[]) => void).apply(null, args);
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ [error prefer-spread-arguments: Prefer spread arguments over 'Function.prototype.apply'.]

declare const obj: {apply(a: any, b: any): any};
obj.apply(null, args);

const nested = {obj};
nested.obj.apply(nested, args);

console.log.apply(null, args);
console.log.apply(console, args);
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ [error prefer-spread-arguments: Prefer spread arguments over 'Function.prototype.apply'.]
console.log.apply(console.log, args);
console.log.apply(obj, args);
console['log'].apply(console, args);
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ [error prefer-spread-arguments: Prefer spread arguments over 'Function.prototype.apply'.]
(console['log'])!.apply(console, args);
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ [error prefer-spread-arguments: Prefer spread arguments over 'Function.prototype.apply'.]
window.console.log.apply(window.console, args);
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ [error prefer-spread-arguments: Prefer spread arguments over 'Function.prototype.apply'.]
window.console.log.apply(window.console!, args);
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ [error prefer-spread-arguments: Prefer spread arguments over 'Function.prototype.apply'.]
window.console.log.apply(window['console'], args);
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ [error prefer-spread-arguments: Prefer spread arguments over 'Function.prototype.apply'.]
window['console'].log.apply(window.console, args);
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ [error prefer-spread-arguments: Prefer spread arguments over 'Function.prototype.apply'.]
window['console'].log.apply(window['console'], args);
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ [error prefer-spread-arguments: Prefer spread arguments over 'Function.prototype.apply'.]

declare let objArr: {fn: (...args: any[]) => void}[];
declare let i: number;
objArr[0].fn.apply(objArr[0], args);
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ [error prefer-spread-arguments: Prefer spread arguments over 'Function.prototype.apply'.]
objArr[0].fn.apply(objArr['0'], args);
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ [error prefer-spread-arguments: Prefer spread arguments over 'Function.prototype.apply'.]
objArr[0].fn.apply(objArr[1], args);
objArr[i].fn.apply(objArr[i], args);
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ [error prefer-spread-arguments: Prefer spread arguments over 'Function.prototype.apply'.]
objArr[objArr.length].fn.apply(objArr[objArr.length], args);
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ [error prefer-spread-arguments: Prefer spread arguments over 'Function.prototype.apply'.]
objArr[objArr.length].fn.apply(objArr[objArr['length']], args);
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ [error prefer-spread-arguments: Prefer spread arguments over 'Function.prototype.apply'.]
objArr[objArr.length - 1].fn.apply(objArr[objArr.length - 1], args);

objArr[Symbol.iterator].apply(objArr, args);
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ [error prefer-spread-arguments: Prefer spread arguments over 'Function.prototype.apply'.]

class C {
#fn!: (...args: any[]) => void;
#obj: {fn: (...args: any[]) => void};
[key: string]: {fn: (...args: any[]) => void};
constructor() {
this.#fn.apply(this, args);
~~~~~~~~~~~~~~~~~~~~~~~~~~ [error prefer-spread-arguments: Prefer spread arguments over 'Function.prototype.apply'.]
this.#fn.apply(new C(), args);
this.#fn.apply(null, args);
this.#obj.fn.apply(this.#obj, args);
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ [error prefer-spread-arguments: Prefer spread arguments over 'Function.prototype.apply'.]
this.#obj.fn.apply(this, args);

this.#obj.fn.apply(this['#obj'], args);
this.#obj.fn.apply(this['obj'], args);
this['#obj'].fn.apply(this.#obj, args);
this['#obj'].fn.apply(this['#obj'], args);
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ [error prefer-spread-arguments: Prefer spread arguments over 'Function.prototype.apply'.]
}
}

class Base {
fn(...args: any[]) {};
}

class Derived extends Base {
fn() {
super.fn.apply(this, args);
~~~~~~~~~~~~~~~~~~~~~~~~~~ [error prefer-spread-arguments: Prefer spread arguments over 'Function.prototype.apply'.]
}
}

fn.apply /* .apply() */(null /* .apply() */, args);
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ [error prefer-spread-arguments: Prefer spread arguments over 'Function.prototype.apply'.]
fn. //
~~~~~~
apply
~~~~~~~
(null, args);
~~~~~~~~~~~~ [error prefer-spread-arguments: Prefer spread arguments over 'Function.prototype.apply'.]

fn.apply //
~~~~~~~~~~~
(null, args);
~~~~~~~~~~~~~~~~ [error prefer-spread-arguments: Prefer spread arguments over 'Function.prototype.apply'.]

fn.apply //
1 change: 1 addition & 0 deletions packages/mimir/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ Rule | Description | Difference to TSLint rule / Why you should use it
[`prefer-namespace-keyword`](docs/prefer-namespace-keyword.md) | :wrench: Prefer `namespace foo {}` over `module foo {}` to avoid confusion with ECMAScript modules. | Same as TSLint's `no-internal-module`.
[`prefer-number-methods`](docs/prefer-number-methods.md) | :mag: :wrench: Prefer ES2015's `Number.isNaN` and `Number.isFinite` over the global `isNaN` and `isFinite`. | No similar rule in TSLint.
[`prefer-object-spread`](docs/prefer-object-spread.md) | :mag: :wrench: Prefer object spread over `Object.assign` for copying properties to a new object. | Performance, better handling of parens in fixer and avoids false positives that would cause a compile error when fixed.
[`prefer-spread-arguments`](docs/prefer-spread-arguments.md) | :mag: :wrench: Prefer spread arguments over `Function.prototype.apply` to call variadic functions. | No similar rule in TSLint.
[`return-never-call`](docs/return-never-call.md) | :mag: Enforces `return`ing or `throw`ing the result of a function of method call that returns `never`. | TSLint has no similar rule.
[`syntaxcheck`](docs/syntaxcheck.md) | :mag: :x: Reports syntax errors as lint errors.| Used to be part of the deprecated `tslint --type-check`
[`trailing-newline`](docs/trailing-newline.md) | :wrench: Enforces a line break at the end of each file. | Nothing fancy here :(
Expand Down
2 changes: 1 addition & 1 deletion packages/mimir/docs/delete-only-optional-property.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# delete-only-optional-property

:mag: requires type information
:mag: requires type information and `strictNullChecks` compiler option

Disallows `delete` of required properties.

Expand Down
50 changes: 50 additions & 0 deletions packages/mimir/docs/prefer-spread-arguments.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
# prefer-spread-arguments

:mag: requires type information and `strictBindCallApply` compiler option
:wrench: fixable

Prefer spread arguments over `Function.prototype.apply` to call variadic functions.

## Rationale

ECMAScript 2015 added a way to syntactically express variadic arguments in function calls. Before you had to use `Function.prototype.apply`.
Spread arguments provide additional safety as its behavior cannot be overridden, which is possible for `Fucntion.prototype.apply`. In addition there is no chance to accidentally call the function with the wrong `thisArg` (value of `this`).

## Examples

:thumbsdown: Examples of incorrect code

```ts
declare const args: string[];

alert.apply(null, args);
alert.apply(undefined, args);
console.log.apply(console, args);
```

:thumbsup: Examples of correct code

```ts
declare const args: string[];

// fixed examples from above
alert(...args);
alert(...args);
console.log(...args);

// intentionally providing a different thisArg
declare const fn: (...args: any[]) => void;
fn.apply(global, args);

// calling a method named 'apply'
const obj = { apply(a: any, b: any) {}}
obj.apply(null, args);
```

## Further Reading

* MDN: [Spread syntax / Spread in function calls](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_syntax#Spread_in_function_calls)

## Related Rules

* [`no-useless-spread`](no-useless-spread.md)
1 change: 1 addition & 0 deletions packages/mimir/recommended.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ rules:
prefer-namespace-keyword: error
prefer-number-methods: error
prefer-object-spread: error
prefer-spread-arguments: error
return-never-call: error
trailing-newline: error
try-catch-return-await: error
6 changes: 1 addition & 5 deletions packages/mimir/src/rules/no-useless-initializer.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { AbstractRule, Replacement, excludeDeclarationFiles } from '@fimbul/ymir';
import * as ts from 'typescript';
import {
isIdentifier,
getChildOfKind,
isFunctionWithBody,
isUnionTypeNode,
Expand All @@ -17,6 +16,7 @@ import {
LateBoundPropertyNames,
getLateBoundPropertyNamesOfPropertyName,
} from 'tsutils';
import { isUndefined } from '../utils';

@excludeDeclarationFiles
export class Rule extends AbstractRule {
Expand Down Expand Up @@ -220,7 +220,3 @@ function typeMaybeUndefined(checker: ts.TypeChecker, type: ts.Type): boolean {
return type.types.some((t) => typeMaybeUndefined(checker, t));
return (type.flags & (ts.TypeFlags.Undefined | ts.TypeFlags.Any | ts.TypeFlags.Unknown)) !== 0;
}

function isUndefined(node: ts.Expression) {
return isIdentifier(node) && node.originalKeywordKind === ts.SyntaxKind.UndefinedKeyword;
}
Loading