Skip to content

Commit 53f468d

Browse files
authored
feature: support custom lint rules (#4118)
* feature: support custom lint rules - Adds a custom lint rule local package. - Tests for custom lint rules (not ran with main tests). - One initial rule to throw an error on any it.skip() or describe.skip() calls. - Updated docs. * refactor(lint): rename lint plugin and format
1 parent 301f0dd commit 53f468d

File tree

11 files changed

+208
-7
lines changed

11 files changed

+208
-7
lines changed

.eslintrc.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ module.exports = {
99
node: true,
1010
mocha: true,
1111
},
12-
plugins: ['@typescript-eslint', 'header', 'no-null'],
12+
plugins: ['@typescript-eslint', 'header', 'no-null', 'aws-toolkits'],
1313
extends: [
1414
'eslint:recommended',
1515
'plugin:@typescript-eslint/eslint-recommended',
@@ -115,5 +115,6 @@ module.exports = {
115115
},
116116
{ lineEndings: 'unix' },
117117
],
118+
'aws-toolkits/no-only-in-tests': 'error',
118119
},
119120
}

.prettierignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,3 +9,4 @@ src/testFixtures/**
99
dist/**
1010
types/*.d.ts
1111
src.gen/**
12+
plugins/*/dist/**

CONTRIBUTING.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -335,6 +335,15 @@ See [docs/cfn-schema-support.md](./docs/cfn-schema-support.md) for how to fix
335335
and improve the JSON schema that provides auto-completion and syntax checking
336336
of SAM and CloudFormation `template.yaml` files.
337337

338+
### Custom Lint Rules
339+
340+
The package.json 'devDependencies' includes `eslint-plugin-aws-toolkits`. This is a local eslint plugin where we define custom lint rules. Additional lint rules and tests for lint rules can be added to this plugin:
341+
342+
1. Define a new rule in `plugins/eslint-plugin-aws-toolkits/lib/rules`.
343+
2. Create a test for your rule in `plugins/eslint-plugin-aws-toolkits/test/rules` and run with `npm run test` in the root directory of `eslint-plugin-aws-toolkits`.
344+
3. Register your rule in `plugins/eslint-plugin-aws-toolkits/index.ts`.
345+
4. Enable your rule in `.eslintrc`.
346+
338347
### AWS SDK generator
339348

340349
When the AWS SDK does not (yet) support a service but you have an API

package-lock.json

Lines changed: 21 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4238,15 +4238,16 @@
42384238
"runInBrowser": "npm run buildBrowser && npx @vscode/test-web --open-devtools --extensionDevelopmentPath=. .",
42394239
"compile": "npm run clean && npm run buildScripts && webpack --mode development && npm run copyFiles",
42404240
"watch": "npm run clean && npm run buildScripts && tsc -watch -p ./",
4241-
"postinstall": "npm run generateTelemetry && npm run generateConfigurationAttributes",
4241+
"postinstall": "npm run generateTelemetry && npm run generateConfigurationAttributes && npm run buildCustomLintPlugin",
42424242
"testCompile": "npm run buildScripts && tsc -p ./ && npm run instrument",
42434243
"test": "npm run testCompile && ts-node ./scripts/test/test.ts && npm run report",
42444244
"testE2E": "npm run testCompile && ts-node ./scripts/test/testE2E.ts && npm run report",
42454245
"testInteg": "npm run testCompile && ts-node ./scripts/test/testInteg.ts && npm run report",
42464246
"lint": "ts-node ./scripts/lint/testLint.ts && npm run format",
42474247
"lintfix": "eslint -c .eslintrc.js --fix --ext .ts . && npm run formatfix",
4248-
"format": "prettier --check src",
4249-
"formatfix": "prettier --write src",
4248+
"buildCustomLintPlugin": "cd ./plugins/eslint-plugin-aws-toolkits && npm run build",
4249+
"format": "prettier --check src plugins",
4250+
"formatfix": "prettier --write src plugins",
42504251
"package": "ts-node ./scripts/build/package.ts",
42514252
"install-plugin": "vsce package -o aws-toolkit-vscode-test.vsix && code --install-extension aws-toolkit-vscode-test.vsix",
42524253
"generateClients": "npm run build -w @amzn/codewhisperer-streaming && ts-node ./scripts/build/generateServiceClient.ts ",
@@ -4311,6 +4312,7 @@
43114312
"eslint-config-prettier": "8.8",
43124313
"eslint-plugin-header": "^3.1.1",
43134314
"eslint-plugin-no-null": "^1.0.2",
4315+
"eslint-plugin-aws-toolkits": "file:plugins/eslint-plugin-aws-toolkits",
43144316
"file-loader": "^6.2.0",
43154317
"glob": "^7.1.7",
43164318
"husky": "^7.0.2",
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
/*!
2+
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
import NoOnlyInTests from './lib/rules/no-only-in-tests'
7+
8+
const rules = {
9+
'no-only-in-tests': NoOnlyInTests,
10+
}
11+
12+
export { rules }
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
/*!
2+
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
import { ESLintUtils } from '@typescript-eslint/utils'
7+
import { AST_NODE_TYPES } from '@typescript-eslint/types'
8+
import { CallExpression, Identifier, MemberExpression } from '@typescript-eslint/types/dist/generated/ast-spec'
9+
import { Rule } from 'eslint'
10+
11+
function isValidExpression(node: CallExpression): MemberExpression | undefined {
12+
const isValid =
13+
node.callee.type === AST_NODE_TYPES.MemberExpression &&
14+
node.callee.object.type === AST_NODE_TYPES.Identifier &&
15+
node.callee.property.type === AST_NODE_TYPES.Identifier
16+
17+
return isValid ? (node.callee as MemberExpression) : undefined
18+
}
19+
20+
export const describeOnlyErrMsg = 'mocha test `.only()` not allowed for `describe`'
21+
export const itOnlyErrMsg = 'mocha test `.only()` not allowed for `it`'
22+
23+
export default ESLintUtils.RuleCreator.withoutDocs({
24+
meta: {
25+
docs: {
26+
description: "disallow mocha's only() from being published in test code",
27+
recommended: 'error',
28+
},
29+
messages: {
30+
describeOnlyErrMsg,
31+
itOnlyErrMsg,
32+
},
33+
type: 'problem',
34+
fixable: 'code',
35+
schema: [],
36+
},
37+
defaultOptions: [],
38+
create(context) {
39+
return {
40+
CallExpression(node) {
41+
if (!isValidExpression(node)) {
42+
return
43+
}
44+
const expr = node.callee as MemberExpression
45+
const property = expr.property as Identifier
46+
const object = expr.object as Identifier
47+
48+
if (property.name !== 'only') {
49+
return
50+
}
51+
52+
if (object.name === 'describe' || object.name === 'it') {
53+
return context.report({
54+
node: node.callee,
55+
messageId: `${object.name}OnlyErrMsg`,
56+
fix: fixer => {
57+
// Range - 1 removes the period in `it.only()`
58+
return fixer.removeRange([property.range[0] - 1, property.range[1]])
59+
},
60+
})
61+
}
62+
},
63+
}
64+
},
65+
}) as unknown as Rule.RuleModule
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
{
2+
"name": "eslint-plugin-aws-toolkits",
3+
"version": "1.0.0",
4+
"description": "Local custom lint rules for AWS Toolkit VSCode",
5+
"main": "dist/index.js",
6+
"scripts": {
7+
"build": "tsc",
8+
"test": "npm run build && mocha dist/test --recursive"
9+
},
10+
"devDependencies": {
11+
"eslint": "^8.26.0",
12+
"mocha": "^10.1.0",
13+
"typescript": "^5.0.4"
14+
},
15+
"engines": {
16+
"npm": "^10.1.0"
17+
},
18+
"license": "Apache-2.0"
19+
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
/*!
2+
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
import { rules } from '../..'
7+
import { describeOnlyErrMsg, itOnlyErrMsg } from '../../lib/rules/no-only-in-tests'
8+
import { getRuleTester } from '../testUtil'
9+
10+
getRuleTester().run('no-only-in-tests', rules['no-only-in-tests'], {
11+
valid: [
12+
"describe('my suite', function () {})",
13+
"describe('my suite', function () { it('does things', () => {})})",
14+
"it('does things', async function () {})",
15+
],
16+
17+
invalid: [
18+
{
19+
code: "describe.only('mySuite', function () { it('does things', async function () {} ) })",
20+
errors: [describeOnlyErrMsg],
21+
output: "describe('mySuite', function () { it('does things', async function () {} ) })",
22+
},
23+
{
24+
code: "describe('mySuite', function() { it.only('does things', async function () { console.log('did things') })})",
25+
errors: [itOnlyErrMsg],
26+
output: "describe('mySuite', function() { it('does things', async function () { console.log('did things') })})",
27+
},
28+
{
29+
code: "describe.only('mySuite', function() { it.only('does things', async function () { console.log('did things') })})",
30+
errors: [describeOnlyErrMsg, itOnlyErrMsg],
31+
output: "describe('mySuite', function() { it('does things', async function () { console.log('did things') })})",
32+
},
33+
{
34+
code: "it.only('does things', async function () { console.log('did things') })",
35+
errors: [itOnlyErrMsg],
36+
output: "it('does things', async function () { console.log('did things') })",
37+
},
38+
],
39+
})
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
/*!
2+
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
import { RuleTester } from 'eslint'
7+
8+
export function getRuleTester() {
9+
return new RuleTester({
10+
// TODO: For tests that need to access TS types, we will need to pass a parser:
11+
// parser: "@typescript-eslint/parser",
12+
parserOptions: {
13+
project: './tsconfig.json',
14+
tsconfigRootDir: __dirname,
15+
ecmaVersion: 2021,
16+
},
17+
})
18+
}

0 commit comments

Comments
 (0)