Skip to content
This repository has been archived by the owner on Jan 19, 2019. It is now read-only.

[FEAT] [no-this-alias] Add rule #232

Merged
merged 12 commits into from
Dec 17, 2018
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ This guarantees 100% compatibility between the plugin and the parser.
| [`typescript/no-non-null-assertion`](./docs/rules/no-non-null-assertion.md) | Disallows non-null assertions using the `!` postfix operator (`no-non-null-assertion` from TSLint) | | |
| [`typescript/no-object-literal-type-assertion`](./docs/rules/no-object-literal-type-assertion.md) | Forbids an object literal to appear in a type assertion expression (`no-object-literal-type-assertion` from TSLint) | | |
| [`typescript/no-parameter-properties`](./docs/rules/no-parameter-properties.md) | Disallow the use of parameter properties in class constructors. (`no-parameter-properties` from TSLint) | | |
| [`typescript/no-this-alias`](./docs/rules/no-this-alias.md) | Disallow aliasing `this` (`no-this-assignment` from TSLint) | | |
| [`typescript/no-triple-slash-reference`](./docs/rules/no-triple-slash-reference.md) | Disallow `/// <reference path="" />` comments (`no-reference` from TSLint) | | |
| [`typescript/no-type-alias`](./docs/rules/no-type-alias.md) | Disallow the use of type aliases (`interface-over-type-literal` from TSLint) | | |
| [`typescript/no-unused-vars`](./docs/rules/no-unused-vars.md) | Prevent TypeScript-specific constructs from being erroneously flagged as unused | :heavy_check_mark: | |
Expand Down
58 changes: 58 additions & 0 deletions docs/rules/no-this-alias.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
# Disallow aliasing `this` (no-this-alias)

This rule prohibts assigning variables to `this`.

## Rule Details

Rationale from TSLint:

> Assigning a variable to `this` instead of properly using arrow lambdas may be a symptom of pre-ES6 practices
> or not managing scope well.
>
> Instead of storing a reference to `this` and using it inside a `function () {`:
>
> ```js
> const self = this;
>
> setTimeout(function () {
> self.doWork();
> });
> ```
>
> Use `() =>` arrow lambdas, as they preserve `this` scope for you:
>
> ```js
> setTimeout(() => {
> this.doWork();
> });
> ```


Examples of **incorrect** code for this rule:

(see the rationale above)

Examples of **correct** code for this rule:

(see the rationale above)

### Options

You can pass an object option:

```json5
{
"typescript/no-this-alias": ["error", {
"allowDestructuring": true, // Allow `const { props, state } = this`; false by default
"allowedNames": ["self"], // Allow `const self = this`; `[]` by default
}],
}
```

## When Not To Use It

If you need to assign `this` to variables, you shouldn’t use this rule.

## Related to

- TSLint: [`no-this-assignment`](https://palantir.github.io/tslint/rules/no-this-assignment/)
70 changes: 70 additions & 0 deletions lib/rules/no-this-alias.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
/**
* @fileoverview Disallow aliasing `this`
* @author Jed Fox
*/
"use strict";

const util = require("../util");

//------------------------------------------------------------------------------
// Rule Definition
//------------------------------------------------------------------------------

module.exports = {
meta: {
docs: {
description: "Disallow aliasing `this`",
extraDescription: [util.tslintRule("no-this-assignment")],
category: "Best Practices",
recommended: false,
url: util.metaDocsUrl("no-this-alias"),
},
fixable: null,
schema: [
{
type: "object",
additionalProperties: false,
properties: {
allowDestructuring: {
type: "boolean",
},
allowedNames: {
type: "array",
items: {
type: "string",
},
},
},
},
],
messages: {
thisAssignment: "Unexpected aliasing of 'this' to local variable.",
thisDestructure:
"Unexpected aliasing of members of 'this' to local variables.",
},
},

create(context) {
const { allowDestructuring = false, allowedNames = [] } =
context.options[0] || {};

return {
VariableDeclarator(node) {
j-f1 marked this conversation as resolved.
Show resolved Hide resolved
const { id, init } = node;

if (init.type !== "ThisExpression") return;
if (allowDestructuring && node.id.type !== "Identifier") return;

if (!allowedNames.includes(id.name)) {
context.report({
node: id,
messageId:
id.type === "Identifier"
? "thisAssignment"
: "thisDestructure",
});
}
},
};
},
};
123 changes: 123 additions & 0 deletions tests/lib/rules/no-this-alias.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
/**
* @fileoverview Disallow aliasing `this`
* Some tests taken from TSLint: https://github.com/palantir/tslint/tree/c7fc99b5/test/rules/no-this-assignment
* @author Jed Fox
*/
"use strict";

//------------------------------------------------------------------------------
// Requirements
//------------------------------------------------------------------------------

const rule = require("../../../lib/rules/no-this-alias"),
RuleTester = require("eslint").RuleTester;

const idError = { messageId: "thisAssignment", type: "Identifier" };
const destructureError = {
messageId: "thisDestructure",
type: "ObjectPattern",
};
const arrayDestructureError = {
messageId: "thisDestructure",
type: "ArrayPattern",
};

//------------------------------------------------------------------------------
// Tests
//------------------------------------------------------------------------------

const ruleTester = new RuleTester({
parser: "typescript-eslint-parser",
});

ruleTester.run("no-this-alias", rule, {
valid: [
"const self = foo(this);",
{
code: `
const { props, state } = this;
const { length } = this;
const { length, toString } = this;
const [foo] = this;
const [foo, bar] = this;
`.trim(),
options: [
{
allowDestructuring: true,
},
],
},
{
code: "const self = this;",
options: [
{
allowedNames: ["self"],
},
],
},
],

invalid: [
{
code: "const self = this;",
options: [
{
allowDestructuring: true,
},
],
errors: [idError],
},
{
code: "const self = this;",
errors: [idError],
},
{
code: "const { props, state } = this;",
errors: [destructureError],
},
{
code: `
var unscoped = this;

function testFunction() {
let inFunction = this;
}
const testLambda = () => {
const inLambda = this;
};
`.trim(),
errors: [idError, idError, idError],
},
{
code: `
class TestClass {
constructor() {
const inConstructor = this;
const asThis: this = this;

const asString = "this";
const asArray = [this];
const asArrayString = ["this"];
}

public act(scope: this = this) {
const inMemberFunction = this;
const { act } = this;
const { act, constructor } = this;
const [foo] = this;
const [foo, bar] = this;
}
}
`.trim(),
errors: [
idError,
idError,
idError,
destructureError,
destructureError,
arrayDestructureError,
arrayDestructureError,
],
},
],
});