Skip to content

Commit

Permalink
[ESLint Plugin] Add a ts-config-include rule #18514 (#18632)
Browse files Browse the repository at this point in the history
* added ts-config-include to src/rules

* added ts-config-include to tests/rules

* added new rules ts-config-include

* Create ts-config-include.md

* run rushx format to fix README.md, ts-config-include.ts, and ts-package-json-engine-is-present.ts

* Fixed the broken README.md

* Fixed the broken README.md 2

* Revert prettier formatting on ts-package-json-engine-is-present.ts

* simplified checking and fixed wording in src/rules/ts-config-include.ts
  • Loading branch information
WeiJun428 authored Nov 18, 2021
1 parent a36287a commit 5a68b0e
Show file tree
Hide file tree
Showing 8 changed files with 306 additions and 2 deletions.
1 change: 1 addition & 0 deletions common/tools/eslint-plugin-azure-sdk/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@ Some rules (see table below) are fixable using the `--fix` ESLint option (added
| [**ts-config-exclude**](https://github.com/Azure/azure-sdk-for-js/blob/main/common/tools/eslint-plugin-azure-sdk/docs/rules/ts-config-exclude.md) | :triangular_flag_on_post: | :heavy_check_mark: | `1.0.0` |
| [**ts-config-forceconsistentcasinginfilenames**](https://github.com/Azure/azure-sdk-for-js/blob/main/common/tools/eslint-plugin-azure-sdk/docs/rules/ts-config-forceconsistentcasinginfilenames.md) | :triangular_flag_on_post: | :heavy_check_mark: | `1.0.0` |
| [**ts-config-importhelpers**](https://github.com/Azure/azure-sdk-for-js/blob/main/common/tools/eslint-plugin-azure-sdk/docs/rules/ts-config-importhelpers.md) | :triangular_flag_on_post: | :heavy_check_mark: | `1.0.0` |
| [**ts-config-include**](https://github.com/Azure/azure-sdk-for-js/blob/main/common/tools/eslint-plugin-azure-sdk/docs/rules/ts-config-include.md) | :triangular_flag_on_post: | :heavy_check_mark: | `1.0.0` |
| [**ts-config-lib**](https://github.com/Azure/azure-sdk-for-js/blob/main/common/tools/eslint-plugin-azure-sdk/docs/rules/ts-config-lib.md) | :triangular_flag_on_post: | :heavy_check_mark: | `1.0.0` |
| [**ts-config-module**](https://github.com/Azure/azure-sdk-for-js/blob/main/common/tools/eslint-plugin-azure-sdk/docs/rules/ts-config-module.md) | :triangular_flag_on_post: | :heavy_check_mark: | `1.0.0` |
| [**ts-config-moduleresolution**](https://github.com/Azure/azure-sdk-for-js/blob/main/common/tools/eslint-plugin-azure-sdk/docs/rules/ts-config-moduleresolution.md) | :triangular_flag_on_post: | :heavy_check_mark: | `1.1.0` |
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
> docs\rules\ts-config-include.md
# ts-config-include

Requires `include` in `tsconfig.json` to at least include `src/**/*.ts`, `test/**/*.ts`, and `samples-dev/**/*.ts`.

This rule is fixable using the `--fix` option.

## Examples

### Good

```json
{
"include": ["src/**/*.ts", "test/**/*.ts", "samples-dev/**/*.ts"]
}
```

```json
{
"include": ["src/**/*.ts", "test/**/*.ts", "samples-dev/**/*.ts", "other/*.ts"]
}
```

### Bad

```json
{
"include": ["src/**/*.ts", "test/**/*.ts"]
}
```

```json
{
"include": []
}
```

```json
{}
```

## When to turn off

Only if the rule breaks.

## [Source](https://azure.github.io/azure-sdk/typescript_design.html#ts-config-include)
1 change: 1 addition & 0 deletions common/tools/eslint-plugin-azure-sdk/src/configs/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ export = {
"@azure/azure-sdk/ts-config-exclude": "error",
"@azure/azure-sdk/ts-config-forceconsistentcasinginfilenames": "error",
"@azure/azure-sdk/ts-config-importhelpers": "error",
"@azure/azure-sdk/ts-config-include": "error",
"@azure/azure-sdk/ts-config-lib": "error",
"@azure/azure-sdk/ts-config-module": "error",
"@azure/azure-sdk/ts-config-moduleresolution": "error",
Expand Down
2 changes: 2 additions & 0 deletions common/tools/eslint-plugin-azure-sdk/src/rules/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import tsConfigEsModuleInterop from "./ts-config-esmoduleinterop";
import tsConfigExclude from "./ts-config-exclude";
import tsConfigForceConsistentCasingInFileNames from "./ts-config-forceconsistentcasinginfilenames";
import tsConfigImportHelpers from "./ts-config-importhelpers";
import tsConfigInclude from "./ts-config-include";
import tsConfigLib from "./ts-config-lib";
import tsConfigModule from "./ts-config-module";
import tsConfigModuleResolution from "./ts-config-moduleresolution";
Expand Down Expand Up @@ -66,6 +67,7 @@ export = {
"ts-config-exclude": tsConfigExclude,
"ts-config-forceconsistentcasinginfilenames": tsConfigForceConsistentCasingInFileNames,
"ts-config-importhelpers": tsConfigImportHelpers,
"ts-config-include": tsConfigInclude,
"ts-config-lib": tsConfigLib,
"ts-config-module": tsConfigModule,
"ts-config-moduleresolution": tsConfigModuleResolution,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.

/**
* @file Rule to force tsconfig.json's "include" value to at least contain "src/**\/*.ts", "test/**\/*.ts", and "samples-dev/**\/*.ts"
* @author Wei Jun Tan
*/

import { Rule } from "eslint";
import { ArrayExpression, Literal, Property } from "estree";
import { arrayToString, getRuleMetaData, getVerifiers, stripPath } from "../utils";

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

export = {
meta: getRuleMetaData(
"ts-config-include",
"force tsconfig.json's 'include' value to at least contain 'src/**/*.ts', 'test/**/*.ts', and 'samples-dev/**/*.ts'",
"code"
),
create: (context: Rule.RuleContext): Rule.RuleListener => {
const verifiers = getVerifiers(context, {
outer: "include"
});
return stripPath(context.getFilename()) === "tsconfig.json"
? ({
// callback functions

// check to see if include exists at the outermost level
"ExpressionStatement > ObjectExpression": verifiers.existsInFile,

// check the node corresponding to include to see if its value contains "src/**/*.ts", "test/**/*.ts", and "samples-dev/**/*.ts"
"ExpressionStatement > ObjectExpression > Property[key.value='include']": (
node: Property
): void => {
// check if the value is an array of literals
if (node.value.type !== "ArrayExpression") {
context.report({
node: node.value,
message: `include is not set to an array`
});
}

const nodeValue = node.value as ArrayExpression;

const nonLiteral = nodeValue.elements.find(
(element: any): boolean => element.type !== "Literal"
);

if (nonLiteral !== undefined && nonLiteral !== null) {
context.report({
node: nonLiteral,
message: `include contains non-literal elements, literal strings are expected instead`
});
}

const expected = ["src/**/*.ts", "test/**/*.ts", "samples-dev/**/*.ts"];
const candidateArray = nodeValue.elements as Literal[];
const candidateValues = candidateArray.map(
(candidate: Literal): unknown => candidate.value
);

// Check if the expected values is included in the array
expected.forEach((value: unknown): void => {
if (!candidateValues.includes(value)) {
candidateValues.push(value);
}
});
if (candidateValues.length > candidateArray.length) {
context.report({
node: nodeValue,
message: `include does not contain ${arrayToString(candidateValues)}`,
fix: (fixer: Rule.RuleFixer): Rule.Fix =>
fixer.replaceText(nodeValue, arrayToString(candidateValues))
});
}
}
} as Rule.RuleListener)
: {};
}
};
1 change: 1 addition & 0 deletions common/tools/eslint-plugin-azure-sdk/tests/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ const ruleList = [
"ts-config-exclude",
"ts-config-forceconsistentcasinginfilenames",
"ts-config-importhelpers",
"ts-config-include",
"ts-config-lib",
"ts-config-module",
"ts-config-moduleresolution",
Expand Down
169 changes: 169 additions & 0 deletions common/tools/eslint-plugin-azure-sdk/tests/rules/ts-config-include.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.

/**
* @file Testing the ts-config-include rule.
* @author Wei Jun Tan
*/

import rule from "../../src/rules/ts-config-include";
import { RuleTester } from "eslint";

//------------------------------------------------------------------------------
// Example files
//------------------------------------------------------------------------------

const exampleTsconfigGood = `{
"compilerOptions": {
/* Basic Options */
"target": "es6" /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017','ES2018' or 'ESNEXT'. */,
"module": "es6" /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */,
"declaration": true /* Generates corresponding '.d.ts' file. */,
"declarationMap": true /* Generates a sourcemap for each corresponding '.d.ts' file. */,
"sourceMap": true /* Generates corresponding '.map' file. */,
"outDir": "./dist-esm" /* Redirect output structure to the directory. */,
"declarationDir": "./typings" /* Output directory for generated declaration files.*/,
"importHelpers": true /* Import emit helpers from 'tslib'. */,
/* Strict Type-Checking Options */
"strict": true /* Enable all strict type-checking options. */,
"noImplicitReturns": true /* Report error when not all code paths in function return a value. */,
/* Additional Checks */
"noUnusedLocals": true /* Report errors on unused locals. */,
/* Module Resolution Options */
"moduleResolution": "node" /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */,
"allowSyntheticDefaultImports": true /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */,
"esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */,
/* Experimental Options */
"forceConsistentCasingInFileNames": true,
/* Other options */
"newLine": "LF" /* Use the specified end of line sequence to be used when emitting files: "crlf" (windows) or "lf" (unix).”*/,
"allowJs": false /* Don't allow JavaScript files to be compiled.*/,
"resolveJsonModule": true
},
"compileOnSave": true,
"exclude": ["typings/**", "./samples/**/*.ts", "node_modules"],
"include": ["src/**/*.ts", "test/**/*.ts", "samples-dev/**/*.ts"]
}`;

const exampleTsconfigBad = `{
"compilerOptions": {
/* Basic Options */
"target": "es6" /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017','ES2018' or 'ESNEXT'. */,
"module": "es6" /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */,
"declaration": true /* Generates corresponding '.d.ts' file. */,
"declarationMap": true /* Generates a sourcemap for each corresponding '.d.ts' file. */,
"sourceMap": true /* Generates corresponding '.map' file. */,
"outDir": "./dist-esm" /* Redirect output structure to the directory. */,
"declarationDir": "./typings" /* Output directory for generated declaration files.*/,
"importHelpers": true /* Import emit helpers from 'tslib'. */,
/* Strict Type-Checking Options */
"strict": true /* Enable all strict type-checking options. */,
"noImplicitReturns": true /* Report error when not all code paths in function return a value. */,
/* Additional Checks */
"noUnusedLocals": true /* Report errors on unused locals. */,
/* Module Resolution Options */
"moduleResolution": "node" /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */,
"allowSyntheticDefaultImports": true /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */,
"esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */,
/* Experimental Options */
"forceConsistentCasingInFileNames": true,
/* Other options */
"newLine": "LF" /* Use the specified end of line sequence to be used when emitting files: "crlf" (windows) or "lf" (unix).”*/,
"allowJs": false /* Don't allow JavaScript files to be compiled.*/,
"resolveJsonModule": true
},
"compileOnSave": true,
"exclude": ["typings/**", "./samples/**/*.ts", "node_modules"],
"include": []
}`;

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

const ruleTester = new RuleTester({
parser: require.resolve("@typescript-eslint/parser"),
parserOptions: {
createDefaultProgram: true,
project: "./tsconfig.json"
}
});

ruleTester.run("ts-config-include", rule, {
valid: [
{
// only the fields we care about
code: '{"include": ["src/**/*.ts", "test/**/*.ts", "samples-dev/**/*.ts"]}',
filename: "tsconfig.json"
},
{
// a full example tsconfig.json
code: exampleTsconfigGood,
filename: "tsconfig.json"
},
{
// incorrect format but in a file we don't care about
code: '{"include": []}',
filename: "not_tsconfig.json"
}
],
invalid: [
{
code: '{"notInclude": ["src/**/*.ts", "test/**/*.ts", "samples-dev/**/*.ts"]}',
filename: "tsconfig.json",
errors: [
{
message: "include does not exist at the outermost level"
}
]
},
{
// exclude is in a nested object
code: '{"outer": {"include": ["src/**/*.ts", "test/**/*.ts", "samples-dev/**/*.ts"]}}',
filename: "tsconfig.json",
errors: [
{
message: "include does not exist at the outermost level"
}
]
},
{
// only the fields we care about
code: '{"include": []}',
filename: "tsconfig.json",
errors: [
{
message: 'include does not contain ["src/**/*.ts", "test/**/*.ts", "samples-dev/**/*.ts"]'
}
],
output: '{"include": ["src/**/*.ts", "test/**/*.ts", "samples-dev/**/*.ts"]}'
},
{
// example file with empty include
code: exampleTsconfigBad,
filename: "tsconfig.json",
errors: [
{
message: 'include does not contain ["src/**/*.ts", "test/**/*.ts", "samples-dev/**/*.ts"]'
}
],
output: exampleTsconfigGood
}
]
});
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ const examplePackageGood = `{
"eslint-detailed-reporter": "^0.8.0",
"eslint-plugin-no-null": "^1.0.2",
"eslint-plugin-no-only-tests": "^2.3.0",
"eslint-plugin-promise": "^4.1.1",
"eslint-plugin-promise": "^4.1.1",
"https-proxy-agent": "^2.2.1",
"karma": "^4.0.1",
"karma-chrome-launcher": "^2.2.0",
Expand Down Expand Up @@ -195,7 +195,7 @@ const examplePackageBad = `{
"eslint-detailed-reporter": "^0.8.0",
"eslint-plugin-no-null": "^1.0.2",
"eslint-plugin-no-only-tests": "^2.3.0",
"eslint-plugin-promise": "^4.1.1",
"eslint-plugin-promise": "^4.1.1",
"https-proxy-agent": "^2.2.1",
"karma": "^4.0.1",
"karma-chrome-launcher": "^2.2.0",
Expand Down

0 comments on commit 5a68b0e

Please sign in to comment.