Skip to content

Commit

Permalink
feature: support custom lint rules (#4118)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
hayemaxi authored Dec 6, 2023
1 parent 301f0dd commit 53f468d
Show file tree
Hide file tree
Showing 11 changed files with 208 additions and 7 deletions.
3 changes: 2 additions & 1 deletion .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ module.exports = {
node: true,
mocha: true,
},
plugins: ['@typescript-eslint', 'header', 'no-null'],
plugins: ['@typescript-eslint', 'header', 'no-null', 'aws-toolkits'],
extends: [
'eslint:recommended',
'plugin:@typescript-eslint/eslint-recommended',
Expand Down Expand Up @@ -115,5 +115,6 @@ module.exports = {
},
{ lineEndings: 'unix' },
],
'aws-toolkits/no-only-in-tests': 'error',
},
}
1 change: 1 addition & 0 deletions .prettierignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,4 @@ src/testFixtures/**
dist/**
types/*.d.ts
src.gen/**
plugins/*/dist/**
9 changes: 9 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -335,6 +335,15 @@ See [docs/cfn-schema-support.md](./docs/cfn-schema-support.md) for how to fix
and improve the JSON schema that provides auto-completion and syntax checking
of SAM and CloudFormation `template.yaml` files.

### Custom Lint Rules

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:

1. Define a new rule in `plugins/eslint-plugin-aws-toolkits/lib/rules`.
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`.
3. Register your rule in `plugins/eslint-plugin-aws-toolkits/index.ts`.
4. Enable your rule in `.eslintrc`.

### AWS SDK generator

When the AWS SDK does not (yet) support a service but you have an API
Expand Down
24 changes: 21 additions & 3 deletions package-lock.json

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

8 changes: 5 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4238,15 +4238,16 @@
"runInBrowser": "npm run buildBrowser && npx @vscode/test-web --open-devtools --extensionDevelopmentPath=. .",
"compile": "npm run clean && npm run buildScripts && webpack --mode development && npm run copyFiles",
"watch": "npm run clean && npm run buildScripts && tsc -watch -p ./",
"postinstall": "npm run generateTelemetry && npm run generateConfigurationAttributes",
"postinstall": "npm run generateTelemetry && npm run generateConfigurationAttributes && npm run buildCustomLintPlugin",
"testCompile": "npm run buildScripts && tsc -p ./ && npm run instrument",
"test": "npm run testCompile && ts-node ./scripts/test/test.ts && npm run report",
"testE2E": "npm run testCompile && ts-node ./scripts/test/testE2E.ts && npm run report",
"testInteg": "npm run testCompile && ts-node ./scripts/test/testInteg.ts && npm run report",
"lint": "ts-node ./scripts/lint/testLint.ts && npm run format",
"lintfix": "eslint -c .eslintrc.js --fix --ext .ts . && npm run formatfix",
"format": "prettier --check src",
"formatfix": "prettier --write src",
"buildCustomLintPlugin": "cd ./plugins/eslint-plugin-aws-toolkits && npm run build",
"format": "prettier --check src plugins",
"formatfix": "prettier --write src plugins",
"package": "ts-node ./scripts/build/package.ts",
"install-plugin": "vsce package -o aws-toolkit-vscode-test.vsix && code --install-extension aws-toolkit-vscode-test.vsix",
"generateClients": "npm run build -w @amzn/codewhisperer-streaming && ts-node ./scripts/build/generateServiceClient.ts ",
Expand Down Expand Up @@ -4311,6 +4312,7 @@
"eslint-config-prettier": "8.8",
"eslint-plugin-header": "^3.1.1",
"eslint-plugin-no-null": "^1.0.2",
"eslint-plugin-aws-toolkits": "file:plugins/eslint-plugin-aws-toolkits",
"file-loader": "^6.2.0",
"glob": "^7.1.7",
"husky": "^7.0.2",
Expand Down
12 changes: 12 additions & 0 deletions plugins/eslint-plugin-aws-toolkits/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
/*!
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/

import NoOnlyInTests from './lib/rules/no-only-in-tests'

const rules = {
'no-only-in-tests': NoOnlyInTests,
}

export { rules }
65 changes: 65 additions & 0 deletions plugins/eslint-plugin-aws-toolkits/lib/rules/no-only-in-tests.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
/*!
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/

import { ESLintUtils } from '@typescript-eslint/utils'
import { AST_NODE_TYPES } from '@typescript-eslint/types'
import { CallExpression, Identifier, MemberExpression } from '@typescript-eslint/types/dist/generated/ast-spec'
import { Rule } from 'eslint'

function isValidExpression(node: CallExpression): MemberExpression | undefined {
const isValid =
node.callee.type === AST_NODE_TYPES.MemberExpression &&
node.callee.object.type === AST_NODE_TYPES.Identifier &&
node.callee.property.type === AST_NODE_TYPES.Identifier

return isValid ? (node.callee as MemberExpression) : undefined
}

export const describeOnlyErrMsg = 'mocha test `.only()` not allowed for `describe`'
export const itOnlyErrMsg = 'mocha test `.only()` not allowed for `it`'

export default ESLintUtils.RuleCreator.withoutDocs({
meta: {
docs: {
description: "disallow mocha's only() from being published in test code",
recommended: 'error',
},
messages: {
describeOnlyErrMsg,
itOnlyErrMsg,
},
type: 'problem',
fixable: 'code',
schema: [],
},
defaultOptions: [],
create(context) {
return {
CallExpression(node) {
if (!isValidExpression(node)) {
return
}
const expr = node.callee as MemberExpression
const property = expr.property as Identifier
const object = expr.object as Identifier

if (property.name !== 'only') {
return
}

if (object.name === 'describe' || object.name === 'it') {
return context.report({
node: node.callee,
messageId: `${object.name}OnlyErrMsg`,
fix: fixer => {
// Range - 1 removes the period in `it.only()`
return fixer.removeRange([property.range[0] - 1, property.range[1]])
},
})
}
},
}
},
}) as unknown as Rule.RuleModule
19 changes: 19 additions & 0 deletions plugins/eslint-plugin-aws-toolkits/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{
"name": "eslint-plugin-aws-toolkits",
"version": "1.0.0",
"description": "Local custom lint rules for AWS Toolkit VSCode",
"main": "dist/index.js",
"scripts": {
"build": "tsc",
"test": "npm run build && mocha dist/test --recursive"
},
"devDependencies": {
"eslint": "^8.26.0",
"mocha": "^10.1.0",
"typescript": "^5.0.4"
},
"engines": {
"npm": "^10.1.0"
},
"license": "Apache-2.0"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/*!
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/

import { rules } from '../..'
import { describeOnlyErrMsg, itOnlyErrMsg } from '../../lib/rules/no-only-in-tests'
import { getRuleTester } from '../testUtil'

getRuleTester().run('no-only-in-tests', rules['no-only-in-tests'], {
valid: [
"describe('my suite', function () {})",
"describe('my suite', function () { it('does things', () => {})})",
"it('does things', async function () {})",
],

invalid: [
{
code: "describe.only('mySuite', function () { it('does things', async function () {} ) })",
errors: [describeOnlyErrMsg],
output: "describe('mySuite', function () { it('does things', async function () {} ) })",
},
{
code: "describe('mySuite', function() { it.only('does things', async function () { console.log('did things') })})",
errors: [itOnlyErrMsg],
output: "describe('mySuite', function() { it('does things', async function () { console.log('did things') })})",
},
{
code: "describe.only('mySuite', function() { it.only('does things', async function () { console.log('did things') })})",
errors: [describeOnlyErrMsg, itOnlyErrMsg],
output: "describe('mySuite', function() { it('does things', async function () { console.log('did things') })})",
},
{
code: "it.only('does things', async function () { console.log('did things') })",
errors: [itOnlyErrMsg],
output: "it('does things', async function () { console.log('did things') })",
},
],
})
18 changes: 18 additions & 0 deletions plugins/eslint-plugin-aws-toolkits/test/testUtil.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
/*!
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/

import { RuleTester } from 'eslint'

export function getRuleTester() {
return new RuleTester({
// TODO: For tests that need to access TS types, we will need to pass a parser:
// parser: "@typescript-eslint/parser",
parserOptions: {
project: './tsconfig.json',
tsconfigRootDir: __dirname,
ecmaVersion: 2021,
},
})
}
17 changes: 17 additions & 0 deletions plugins/eslint-plugin-aws-toolkits/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
"compilerOptions": {
"baseUrl": ".",
"incremental": true,
"module": "commonjs",
"target": "es2021",
"outDir": "dist",
"lib": ["dom", "es2021"],
"sourceMap": true,
"moduleResolution": "node",
"resolveJsonModule": true,
"strict": true,
"noUnusedLocals": true,
"skipLibCheck": true
},
"include": ["lib/**/*", "test/**/*", "index.ts"]
}

0 comments on commit 53f468d

Please sign in to comment.