Repository: nzakas/eslint-plugin-typescript
Pull request #53 (2017-08-19 04:36:41)

New: Add no-parameter-properties
This includes

New rule no-parameter-properties
Corresponding test
Corresponding documentation
Update to README.md to include the new rule

Related to #5 (TSLint compatibility)

JamesHenry: I resolved the conflicts on here too @weirdpattern, so make sure you pull before continuing to work on it 👍 

weirdpattern: I did the same as you did, use github to resolve the conflict...

Patch:
diff --git a/README.md b/README.md
index d2cba8b..42c1ad7 100644
--- a/README.md
+++ b/README.md
@@ -62,6 +62,7 @@ Then configure the rules you want to use under the rules section.
 * [`typescript/member-ordering`](./docs/rules/member-ordering.md) — enforces a standard member declaration order. (`member-ordering` from TSLint)
 * [`typescript/no-unused-vars`](./docs/rules/no-unused-vars.md) — prevents TypeScript-specific constructs from being erroneously flagged as unused
 * [`typescript/adjacent-overload-signatures`](./docs/rules/adjacent-overload-signatures.md) — enforces member overloads to be consecutive.
+* [`typescript/no-parameter-properties`](./docs/rules/no-parameter-properties.md) - disallows parameter properties in class constructors. (`no-parameter-properties` from TSLint)
 * [`typescript/class-name-casing`](./docs/rules/adjacent-overload-signatures.md) - enforces PascalCased class and interface names. (`class-name` from TSLint)
 * [`typescript/member-delimiter-style`](./docs/rules/member-delimiter-style.md) - enforces a member delimiter style in interfaces and type literals.
 * [`typescript/no-empty-interface`](./docs/rules/no-empty-interface.md) - disallows the declaration of empty interfaces. (`no-empty-interface` from TSLint)
diff --git a/docs/rules/no-parameter-properties.md b/docs/rules/no-parameter-properties.md
new file mode 100644
index 0000000..df85a7a
--- /dev/null
+++ b/docs/rules/no-parameter-properties.md
@@ -0,0 +1,342 @@
+# Disallows parameter properties in class constructors.
+
+Parameter properties can be confusing to those new to TypeScript as they are less explicit than other ways
+of declaring and initializing class members.
+
+## Rule Details
+
+This rule disallows the use of parameter properties in constructors, forcing the user to explicitly
+declare all properties in the class.
+
+## Options
+
+This rule, in its default state, does not require any argument and would completely disallow the use of parameter properties.
+If you would like to allow certain types of parameter properties then you may pass an object with the following options:
+- `allows`, an array containing one or more fo the allowed modifiers. Valid values are:
+  - `readonly`, allows **readonly** parameter properties.
+  - `private`, allows **private** parameter properties.
+  - `protected`, allows **protected** parameter properties.
+  - `public`, allows **public** parameter properties.
+  - `private readonly`, allows **private readonly** parameter properties.
+  - `protected readonly`, allows **protected readonly** parameter properties.
+  - `public readonly`, allows **public readonly** parameter properties.
+
+### default
+Examples of **incorrect** code for this rule with no options at all:
+```ts
+class Foo {
+    constructor(readonly name: string) {}
+}
+
+class Foo {
+    constructor(private name: string) {}
+}
+
+class Foo {
+    constructor(protected name: string) {}
+}
+
+class Foo {
+    constructor(public name: string) {}
+}
+
+class Foo {
+    constructor(private readonly name: string) {}
+}
+
+class Foo {
+    constructor(protected readonly name: string) {}
+}
+
+class Foo {
+    constructor(public readonly name: string) {}
+}
+```
+
+Examples of **correct** code for this rule with no options at all:
+```ts
+class Foo {
+    constructor(name: string) {}
+}
+```
+
+### readonly
+Examples of **incorrect** code for the `{ "allows": ["readonly"] }` options:
+```ts
+class Foo {
+    constructor(private name: string) {}
+}
+
+class Foo {
+    constructor(protected name: string) {}
+}
+
+class Foo {
+    constructor(public name: string) {}
+}
+
+class Foo {
+    constructor(private readonly name: string) {}
+}
+
+class Foo {
+    constructor(protected readonly name: string) {}
+}
+
+class Foo {
+    constructor(public readonly name: string) {}
+}
+```
+
+Examples of **correct** code for the `{ "allows": ["readonly"] }` options:
+```ts
+class Foo {
+    constructor(name: string) {}
+}
+
+class Foo {
+    constructor(readonly name: string) {}
+}
+```
+
+### private
+Examples of **incorrect** code for the `{ "allows": ["private"] }` options:
+```ts
+class Foo {
+    constructor(readonly name: string) {}
+}
+
+class Foo {
+    constructor(protected name: string) {}
+}
+
+class Foo {
+    constructor(public name: string) {}
+}
+
+class Foo {
+    constructor(private readonly name: string) {}
+}
+
+class Foo {
+    constructor(protected readonly name: string) {}
+}
+
+class Foo {
+    constructor(public readonly name: string) {}
+}
+```
+
+Examples of **correct** code for the `{ "allows": ["private"] }` options:
+```ts
+class Foo {
+    constructor(name: string) {}
+}
+
+class Foo {
+    constructor(private name: string) {}
+}
+```
+
+### protected
+Examples of **incorrect** code for the `{ "allows": ["protected"] }` options:
+```ts
+class Foo {
+    constructor(readonly name: string) {}
+}
+
+class Foo {
+    constructor(private name: string) {}
+}
+
+class Foo {
+    constructor(public name: string) {}
+}
+
+class Foo {
+    constructor(private readonly name: string) {}
+}
+
+class Foo {
+    constructor(protected readonly name: string) {}
+}
+
+class Foo {
+    constructor(public readonly name: string) {}
+}
+```
+
+Examples of **correct** code for the `{ "allows": ["protected"] }` options:
+```ts
+class Foo {
+    constructor(name: string) {}
+}
+
+class Foo {
+    constructor(protected name: string) {}
+}
+```
+
+### public
+Examples of **incorrect** code for the `{ "allows": ["public"] }` options:
+```ts
+class Foo {
+    constructor(readonly name: string) {}
+}
+
+class Foo {
+    constructor(private name: string) {}
+}
+
+class Foo {
+    constructor(protected name: string) {}
+}
+
+class Foo {
+    constructor(private readonly name: string) {}
+}
+
+class Foo {
+    constructor(protected readonly name: string) {}
+}
+
+class Foo {
+    constructor(public readonly name: string) {}
+}
+```
+
+Examples of **correct** code for the `{ "allows": ["public"] }` options:
+```ts
+class Foo {
+    constructor(name: string) {}
+}
+
+class Foo {
+    constructor(public name: string) {}
+}
+```
+
+### private readonly
+Examples of **incorrect** code for the `{ "allows": ["private readonly"] }` options:
+```ts
+class Foo {
+    constructor(readonly name: string) {}
+}
+
+class Foo {
+    constructor(private name: string) {}
+}
+
+class Foo {
+    constructor(protected name: string) {}
+}
+
+class Foo {
+    constructor(public name: string) {}
+}
+
+class Foo {
+    constructor(protected readonly name: string) {}
+}
+
+class Foo {
+    constructor(public readonly name: string) {}
+}
+```
+
+Examples of **correct** code for the `{ "allows": ["private readonly"] }` options:
+```ts
+class Foo {
+    constructor(name: string) {}
+}
+
+class Foo {
+    constructor(private readonly name: string) {}
+}
+```
+
+### protected readonly
+Examples of **incorrect** code for the `{ "allows": ["protected readonly"] }` options:
+```ts
+class Foo {
+    constructor(readonly name: string) {}
+}
+
+class Foo {
+    constructor(private name: string) {}
+}
+
+class Foo {
+    constructor(protected name: string) {}
+}
+
+class Foo {
+    constructor(public name: string) {}
+}
+
+class Foo {
+    constructor(private readonly name: string) {}
+}
+
+class Foo {
+    constructor(public readonly name: string) {}
+}
+```
+
+Examples of **correct** code for the `{ "allows": ["protected readonly"] }` options:
+```ts
+class Foo {
+    constructor(name: string) {}
+}
+
+class Foo {
+    constructor(protected readonly name: string) {}
+}
+```
+
+### public readonly
+Examples of **incorrect** code for the `{ "allows": ["public readonly"] }` options:
+```ts
+class Foo {
+    constructor(readonly name: string) {}
+}
+
+class Foo {
+    constructor(private name: string) {}
+}
+
+class Foo {
+    constructor(protected name: string) {}
+}
+
+class Foo {
+    constructor(public name: string) {}
+}
+
+class Foo {
+    constructor(private readonly name: string) {}
+}
+
+class Foo {
+    constructor(protected readonly name: string) {}
+}
+```
+
+Examples of **correct** code for the `{ "allows": ["public readonly"] }` options:
+```ts
+class Foo {
+    constructor(name: string) {}
+}
+
+class Foo {
+    constructor(public readonly name: string) {}
+}
+```
+
+## When Not To Use It
+
+If you don't care about the using parameter properties in constructors, then you will not need this rule.
+
+## Compatibility
+
+* TSLint: [no-parameter-properties](https://palantir.github.io/tslint/rules/no-parameter-properties/)
diff --git a/lib/rules/no-parameter-properties.js b/lib/rules/no-parameter-properties.js
new file mode 100644
index 0000000..456c39f
--- /dev/null
+++ b/lib/rules/no-parameter-properties.js
@@ -0,0 +1,86 @@
+/**
+ * @fileoverview Disallows parameter properties in class constructors.
+ * @author Patricio Trevino
+ */
+"use strict";
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+module.exports = {
+    meta: {
+        docs: {
+            description: "Disallows parameter properties in class constructors.",
+            category: "TypeScript"
+        },
+        schema: [
+            {
+                type: "object",
+                properties: {
+                    allows: {
+                        type: "array",
+                        items: {
+                            enum: [
+                                "readonly",
+                                "private",
+                                "protected",
+                                "public",
+                                "private readonly",
+                                "protected readonly",
+                                "public readonly"
+                            ]
+                        },
+                        minItems: 1
+                    }
+                },
+                additionalProperties: false
+            }
+        ]
+    },
+
+    create(context) {
+        const options = context.options[0] || {};
+        const allows = options.allows || [];
+
+        //----------------------------------------------------------------------
+        // Helpers
+        //----------------------------------------------------------------------
+
+        /**
+         * Gets the modifiers of `node`.
+         * @param {ASTNode} node the node to be inspected.
+         * @returns {string} the modifiers of node
+         * @private
+         */
+        function getModifiers(node) {
+            const modifiers = [];
+
+            modifiers.push(node.accessibility);
+            if (node.readonly || node.isReadonly) {
+                modifiers.push("readonly");
+            }
+
+            return modifiers.filter(Boolean).join(" ");
+        }
+
+        //----------------------------------------------------------------------
+        // Public
+        //----------------------------------------------------------------------
+        return {
+            TSParameterProperty(node) {
+                const modifiers = getModifiers(node);
+
+                if (allows.indexOf(modifiers) === -1) {
+                    context.report({
+                        node,
+                        message: "Property {{parameter}} cannot be declared in the constructor",
+                        data: {
+                            parameter: node.parameter.name
+                        }
+                    });
+                }
+            }
+        };
+    }
+};


Test patch:
diff --git a/tests/lib/rules/no-parameter-properties.js b/tests/lib/rules/no-parameter-properties.js
new file mode 100644
index 0000000..58bf5e2
--- /dev/null
+++ b/tests/lib/rules/no-parameter-properties.js
@@ -0,0 +1,616 @@
+/**
+ * @fileoverview Disallows parameter properties in class constructors.
+ * @author Patricio Trevino
+ */
+"use strict";
+
+//------------------------------------------------------------------------------
+// Requirements
+//------------------------------------------------------------------------------
+
+const rule = require("../../../lib/rules/no-parameter-properties"),
+    RuleTester = require("eslint").RuleTester;
+
+//------------------------------------------------------------------------------
+// Tests
+//------------------------------------------------------------------------------
+
+const ruleTester = new RuleTester();
+
+ruleTester.run("no-parameter-properties", rule, {
+    valid: [
+        {
+            code: `
+class Foo {
+    constructor(name: string) {}
+}
+            `,
+            parser: "typescript-eslint-parser"
+        },
+        {
+            code: `
+class Foo {
+    constructor(name: string, age: number) {}
+}
+            `,
+            parser: "typescript-eslint-parser"
+        },
+        {
+            code: `
+class Foo {
+    constructor(name: string);
+    constructor(name: string, age?: number) {}
+}
+            `,
+            parser: "typescript-eslint-parser"
+        },
+        {
+            code: `
+class Foo {
+    constructor(readonly name: string) { }
+}
+            `,
+            options: [{ allows: ["readonly"] }],
+            parser: "typescript-eslint-parser"
+        },
+        {
+            code: `
+class Foo {
+    constructor(private name: string) { }
+}
+            `,
+            options: [{ allows: ["private"] }],
+            parser: "typescript-eslint-parser"
+        },
+        {
+            code: `
+class Foo {
+    constructor(protected name: string) { }
+}
+            `,
+            options: [{ allows: ["protected"] }],
+            parser: "typescript-eslint-parser"
+        },
+        {
+            code: `
+class Foo {
+    constructor(public name: string) { }
+}
+            `,
+            options: [{ allows: ["public"] }],
+            parser: "typescript-eslint-parser"
+        },
+        {
+            code: `
+class Foo {
+    constructor(private readonly name: string) { }
+}
+            `,
+            options: [{ allows: ["private readonly"] }],
+            parser: "typescript-eslint-parser"
+        },
+        {
+            code: `
+class Foo {
+    constructor(protected readonly name: string) { }
+}
+            `,
+            options: [{ allows: ["protected readonly"] }],
+            parser: "typescript-eslint-parser"
+        },
+        {
+            code: `
+class Foo {
+    constructor(public readonly name: string) { }
+}
+            `,
+            options: [{ allows: ["public readonly"] }],
+            parser: "typescript-eslint-parser"
+        },
+        {
+            code: `
+class Foo {
+    constructor(readonly name: string, private age: number) { }
+}
+            `,
+            options: [{ allows: ["readonly", "private"] }],
+            parser: "typescript-eslint-parser"
+        },
+        {
+            code: `
+class Foo {
+    constructor(public readonly name: string, private age: number) { }
+}
+            `,
+            options: [{ allows: ["public readonly", "private"] }],
+            parser: "typescript-eslint-parser"
+        }
+    ],
+    invalid: [
+        {
+            code: `
+class Foo {
+    constructor(readonly name: string) {}
+}
+            `,
+            parser: "typescript-eslint-parser",
+            errors: [
+                {
+                    message: "Property name cannot be declared in the constructor",
+                    line: 3,
+                    column: 17
+                }
+            ]
+        },
+        {
+            code: `
+class Foo {
+    constructor(private name: string) {}
+}
+            `,
+            parser: "typescript-eslint-parser",
+            errors: [
+                {
+                    message: "Property name cannot be declared in the constructor",
+                    line: 3,
+                    column: 17
+                }
+            ]
+        },
+        {
+            code: `
+class Foo {
+    constructor(protected name: string) {}
+}
+            `,
+            parser: "typescript-eslint-parser",
+            errors: [
+                {
+                    message: "Property name cannot be declared in the constructor",
+                    line: 3,
+                    column: 17
+                }
+            ]
+        },
+        {
+            code: `
+class Foo {
+    constructor(public name: string) {}
+}
+            `,
+            parser: "typescript-eslint-parser",
+            errors: [
+                {
+                    message: "Property name cannot be declared in the constructor",
+                    line: 3,
+                    column: 17
+                }
+            ]
+        },
+        {
+            code: `
+class Foo {
+    constructor(private readonly name: string) {}
+}
+            `,
+            parser: "typescript-eslint-parser",
+            errors: [
+                {
+                    message: "Property name cannot be declared in the constructor",
+                    line: 3,
+                    column: 17
+                }
+            ]
+        },
+        {
+            code: `
+class Foo {
+    constructor(protected readonly name: string) {}
+}
+            `,
+            parser: "typescript-eslint-parser",
+            errors: [
+                {
+                    message: "Property name cannot be declared in the constructor",
+                    line: 3,
+                    column: 17
+                }
+            ]
+        },
+        {
+            code: `
+class Foo {
+    constructor(public readonly name: string) {}
+}
+            `,
+            parser: "typescript-eslint-parser",
+            errors: [
+                {
+                    message: "Property name cannot be declared in the constructor",
+                    line: 3,
+                    column: 17
+                }
+            ]
+        },
+        {
+            code: `
+class Foo {
+    constructor(public name: string, age: number) {}
+}
+            `,
+            parser: "typescript-eslint-parser",
+            errors: [
+                {
+                    message: "Property name cannot be declared in the constructor",
+                    line: 3,
+                    column: 17
+                }
+            ]
+        },
+        {
+            code: `
+class Foo {
+    constructor(private name: string, private age: number) {}
+}
+            `,
+            parser: "typescript-eslint-parser",
+            errors: [
+                {
+                    message: "Property name cannot be declared in the constructor",
+                    line: 3,
+                    column: 17
+                },
+                {
+                    message: "Property age cannot be declared in the constructor",
+                    line: 3,
+                    column: 39
+                }
+            ]
+        },
+        {
+            code: `
+class Foo {
+    constructor(protected name: string, protected age: number) {}
+}
+            `,
+            parser: "typescript-eslint-parser",
+            errors: [
+                {
+                    message: "Property name cannot be declared in the constructor",
+                    line: 3,
+                    column: 17
+                },
+                {
+                    message: "Property age cannot be declared in the constructor",
+                    line: 3,
+                    column: 41
+                }
+            ]
+        },
+        {
+            code: `
+class Foo {
+    constructor(public name: string, public age: number) {}
+}
+            `,
+            parser: "typescript-eslint-parser",
+            errors: [
+                {
+                    message: "Property name cannot be declared in the constructor",
+                    line: 3,
+                    column: 17
+                },
+                {
+                    message: "Property age cannot be declared in the constructor",
+                    line: 3,
+                    column: 38
+                }
+            ]
+        },
+        {
+            code: `
+class Foo {
+    constructor(name: string);
+    constructor(private name: string, age?: number) {}
+}
+            `,
+            parser: "typescript-eslint-parser",
+            errors: [
+                {
+                    message: "Property name cannot be declared in the constructor",
+                    line: 4,
+                    column: 17
+                }
+            ]
+        },
+        {
+            code: `
+class Foo {
+    constructor(private name: string);
+    constructor(private name: string, age?: number) {}
+}
+            `,
+            parser: "typescript-eslint-parser",
+            errors: [
+                {
+                    message: "Property name cannot be declared in the constructor",
+                    line: 3,
+                    column: 17
+                },
+                {
+                    message: "Property name cannot be declared in the constructor",
+                    line: 4,
+                    column: 17
+                }
+            ]
+        },
+        {
+            code: `
+class Foo {
+    constructor(private name: string);
+    constructor(private name: string, private age?: number) {}
+}
+            `,
+            parser: "typescript-eslint-parser",
+            errors: [
+                {
+                    message: "Property name cannot be declared in the constructor",
+                    line: 3,
+                    column: 17
+                },
+                {
+                    message: "Property name cannot be declared in the constructor",
+                    line: 4,
+                    column: 17
+                },
+                {
+                    message: "Property age cannot be declared in the constructor",
+                    line: 4,
+                    column: 39
+                }
+            ]
+        },
+        {
+            code: `
+class Foo {
+    constructor(name: string);
+    constructor(protected name: string, age?: number) {}
+}
+            `,
+            parser: "typescript-eslint-parser",
+            errors: [
+                {
+                    message: "Property name cannot be declared in the constructor",
+                    line: 4,
+                    column: 17
+                }
+            ]
+        },
+        {
+            code: `
+class Foo {
+    constructor(protected name: string);
+    constructor(protected name: string, age?: number) {}
+}
+            `,
+            parser: "typescript-eslint-parser",
+            errors: [
+                {
+                    message: "Property name cannot be declared in the constructor",
+                    line: 3,
+                    column: 17
+                },
+                {
+                    message: "Property name cannot be declared in the constructor",
+                    line: 4,
+                    column: 17
+                }
+            ]
+        },
+        {
+            code: `
+class Foo {
+    constructor(protected name: string);
+    constructor(protected name: string, protected age?: number) {}
+}
+            `,
+            parser: "typescript-eslint-parser",
+            errors: [
+                {
+                    message: "Property name cannot be declared in the constructor",
+                    line: 3,
+                    column: 17
+                },
+                {
+                    message: "Property name cannot be declared in the constructor",
+                    line: 4,
+                    column: 17
+                },
+                {
+                    message: "Property age cannot be declared in the constructor",
+                    line: 4,
+                    column: 41
+                }
+            ]
+        },
+        {
+            code: `
+class Foo {
+    constructor(name: string);
+    constructor(public name: string, age?: number) {}
+}
+            `,
+            parser: "typescript-eslint-parser",
+            errors: [
+                {
+                    message: "Property name cannot be declared in the constructor",
+                    line: 4,
+                    column: 17
+                }
+            ]
+        },
+        {
+            code: `
+class Foo {
+    constructor(public name: string);
+    constructor(public name: string, age?: number) {}
+}
+            `,
+            parser: "typescript-eslint-parser",
+            errors: [
+                {
+                    message: "Property name cannot be declared in the constructor",
+                    line: 3,
+                    column: 17
+                },
+                {
+                    message: "Property name cannot be declared in the constructor",
+                    line: 4,
+                    column: 17
+                }
+            ]
+        },
+        {
+            code: `
+class Foo {
+    constructor(public name: string);
+    constructor(public name: string, public age?: number) {}
+}
+            `,
+            parser: "typescript-eslint-parser",
+            errors: [
+                {
+                    message: "Property name cannot be declared in the constructor",
+                    line: 3,
+                    column: 17
+                },
+                {
+                    message: "Property name cannot be declared in the constructor",
+                    line: 4,
+                    column: 17
+                },
+                {
+                    message: "Property age cannot be declared in the constructor",
+                    line: 4,
+                    column: 38
+                }
+            ]
+        },
+
+
+
+        {
+            code: `
+class Foo {
+    constructor(readonly name: string) {}
+}
+            `,
+            options: [{ allows: ["private"] }],
+            parser: "typescript-eslint-parser",
+            errors: [
+                {
+                    message: "Property name cannot be declared in the constructor",
+                    line: 3,
+                    column: 17
+                }
+            ]
+        },
+        {
+            code: `
+class Foo {
+    constructor(private name: string) {}
+}
+            `,
+            options: [{ allows: ["readonly"] }],
+            parser: "typescript-eslint-parser",
+            errors: [
+                {
+                    message: "Property name cannot be declared in the constructor",
+                    line: 3,
+                    column: 17
+                }
+            ]
+        },
+        {
+            code: `
+class Foo {
+    constructor(protected name: string) {}
+}
+            `,
+            options: [{ allows: ["readonly", "private", "public", "protected readonly"] }],
+            parser: "typescript-eslint-parser",
+            errors: [
+                {
+                    message: "Property name cannot be declared in the constructor",
+                    line: 3,
+                    column: 17
+                }
+            ]
+        },
+        {
+            code: `
+class Foo {
+    constructor(public name: string) {}
+}
+            `,
+            options: [{ allows: ["readonly", "private", "protected", "protected readonly", "public readonly"] }],
+            parser: "typescript-eslint-parser",
+            errors: [
+                {
+                    message: "Property name cannot be declared in the constructor",
+                    line: 3,
+                    column: 17
+                }
+            ]
+        },
+        {
+            code: `
+class Foo {
+    constructor(private readonly name: string) {}
+}
+            `,
+            options: [{ allows: ["readonly", "private"] }],
+            parser: "typescript-eslint-parser",
+            errors: [
+                {
+                    message: "Property name cannot be declared in the constructor",
+                    line: 3,
+                    column: 17
+                }
+            ]
+        },
+        {
+            code: `
+class Foo {
+    constructor(protected readonly name: string) {}
+}
+            `,
+            options: [{ allows: ["readonly", "protected", "private readonly", "public readonly"] }],
+            parser: "typescript-eslint-parser",
+            errors: [
+                {
+                    message: "Property name cannot be declared in the constructor",
+                    line: 3,
+                    column: 17
+                }
+            ]
+        },
+        {
+            code: `
+class Foo {
+    constructor(private name: string);
+    constructor(private name: string, protected age?: number) {}
+}
+            `,
+            options: [{ allows: ["private"] }],
+            parser: "typescript-eslint-parser",
+            errors: [
+                {
+                    message: "Property age cannot be declared in the constructor",
+                    line: 4,
+                    column: 39
+                }
+            ]
+        }
+    ]
+});