Skip to content

Commit

Permalink
Add a custom eslint rule to check the constructors
Browse files Browse the repository at this point in the history
  • Loading branch information
Jamie Lynch committed Dec 30, 2020
1 parent d89c282 commit 19c9dfb
Show file tree
Hide file tree
Showing 7 changed files with 172 additions and 4 deletions.
3 changes: 2 additions & 1 deletion .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,11 @@ module.exports = {
tsconfigRootDir: __dirname,
project: ["./tsconfig.eslint.json"],
},
plugins: ["@typescript-eslint"],
plugins: ["@typescript-eslint", "custom-rules"],
rules: {
"@typescript-eslint/no-inferrable-types": 0,
"import/no-namespace": 2,
"custom-rules/valid-constructors": 2,
},
root: true,
ignorePatterns: ["**/*.js", "node_modules"],
Expand Down
7 changes: 7 additions & 0 deletions eslint/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# Custom ESLint Rules

This directory defines any custom rules to be checked against the @guardian/cdk library.

These rules should all have a corresponding [ADR](../docs/architecture-design-records).

More information on building custom rules can be found on the [eslint site](https://eslint.org/docs/developer-guide/working-with-rules).
5 changes: 5 additions & 0 deletions eslint/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
module.exports = {
rules: {
"valid-constructors": require("./rules/valid-constructors"),
},
};
5 changes: 5 additions & 0 deletions eslint/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"name": "eslint-plugin-custom-rules",
"version": "1.0.0",
"main": "index.js"
}
141 changes: 141 additions & 0 deletions eslint/rules/valid-constructors.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
// Rules
// 1. Must be at least 2 parameters
// 2. Can't be more than 3 parameters
// 3. First parameter must be called scope
// 4. First parameter must be of type GuStack
// 5. Second parameter must be called id
// 6. Second parameter must be of type string
// 7. Third parameter (if exists) must called props
// 8. If third parameter is present and non-optional then the second parameter should not be initialised
// TODO: 9. If all values in third type are optional then parameter should be optional
// 10. Third parameter type should be custom

module.exports = {
meta: {
type: "suggestion",
docs: {
description: "ensure constructors conform with agreed pattern",
category: "Best Practices",
url: "https://github.com/guardian/cdk/blob/main/docs/architecture-decision-records/002-component-constuctors.md",
},
schema: [],
},

create(context) {
return {
MethodDefinition(node) {
if (node.kind !== "constructor") return null;

const params = node.value.params;

// 1. Must be at least 2 parameters
if (!Array.isArray(params) || params.length < 2) {
return context.report(
node,
params.loc,
"Construct or pattern constructors must take at least a scope and an id parameter"
);
}

// 2. Can't be more than 3 parameters
if (params.length > 3) {
return context.report(
node,
params.loc,
"Construct or pattern constructors can only take scope, id and props parameters"
);
}

const scope = params[0];

// 3. First parameter must be called scope
if (scope.name !== "scope") {
return context.report(
node,
scope.loc,
`The first parameter in a construct or pattern contructor must be called scope`
);
}

// 4. First parameter must be of type GuStack
if (scope.typeAnnotation.typeAnnotation.typeName.name !== "GuStack") {
return context.report(
node,
scope.typeAnnotation.typeAnnotation.typeName.loc,
`The first parameter in a construct or pattern contructor must be of type GuStack`
);
}

const id = params[1];

// 5. Second parameter must be called id
if (
(id.type === "Identifier" && id.name !== "id") ||
(id.type === "AssignmentPattern" && id.left.name !== "id")
) {
return context.report(
node,
id.loc,
`The second parameter in a construct or pattern contructor must be called id`
);
}

// 6. Second parameter must be of type string
if (
(id.type === "Identifier" && id.typeAnnotation.typeAnnotation.type !== "TSStringKeyword") ||
(id.type === "AssignmentPattern" && id.left.typeAnnotation.typeAnnotation.type !== "TSStringKeyword")
) {
return context.report(
node,
id.typeAnnotation.typeAnnotation.typeName.loc,
`The second parameter in a construct or pattern contructor must be of type string`
);
}

if (params.length === 3) {
const props = params[2];

// 7. Third parameter (if exists) must called props
if (
(props.type === "Identifier" && props.name !== "props") ||
(props.type === "AssignmentPattern" && props.left.name !== "props")
) {
return context.report(
node,
props.loc,
`The third parameter in a construct or pattern contructor must be called props`
);
}

// 10. Third parameter type should be custom
if (
(props.type === "Identifier" && props.typeAnnotation.typeAnnotation.type !== "TSTypeReference") ||
(props.type === "AssignmentPattern" && props.left.typeAnnotation.typeAnnotation.type !== "TSTypeReference")
) {
return context.report(
node,
props.loc,
`The third parameter in a construct or pattern contructor must be a custom type`
);
}
}

// 8. If third parameter is present and non-optional then the second parameter should not be initialised
if (
params.length === 3 &&
params[2].type !== "AssignmentPattern" &&
!params[2].optional &&
id.type === "AssignmentPattern"
) {
return context.report(
node,
id.loc,
`The second parameter cannot be initialised if there is a non-optional third parameter`
);
}

return null;
},
};
},
};
14 changes: 11 additions & 3 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
"eslint-plugin-eslint-comments": "^3.2.0",
"eslint-plugin-import": "^2.22.1",
"eslint-plugin-prettier": "^3.3.0",
"eslint-plugin-custom-rules": "file:eslint",
"jest": "^26.4.2",
"np": "^7.0.0",
"prettier": "^2.2.1",
Expand Down

0 comments on commit 19c9dfb

Please sign in to comment.