linter-bundle 3.10.0 → 4.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -92,7 +92,7 @@ module.exports = {
92
92
  *
93
93
  * @see https://github.com/sindresorhus/eslint-plugin-unicorn
94
94
  */
95
- 'unicorn/filename-case': ['error', {
95
+ 'unicorn/filename-case': ['off', { // Disabled in favour of the linter-bundle `files` task
96
96
  cases: {
97
97
  kebabCase: true
98
98
  }
@@ -2,6 +2,7 @@
2
2
  * @file Settings for React code in TypeScript (TSX) files.
3
3
  */
4
4
 
5
+ const config = require('../helper/config.js');
5
6
  const ensureType = require('../helper/ensure-type');
6
7
 
7
8
  module.exports = {
@@ -90,11 +91,11 @@ module.exports = {
90
91
  'react/forbid-component-props': ['error', { forbid: [
91
92
  {
92
93
  propName: 'className',
93
- allowedFor: ensureType.array(global.linterBundleSettings?.overrides?.react?.['react/forbid-component-props']?.allowClassNameFor)
94
+ allowedFor: ensureType.array(config.ts?.overrides?.react?.['react/forbid-component-props']?.allowClassNameFor)
94
95
  },
95
96
  {
96
97
  propName: 'style',
97
- allowedFor: ensureType.array(global.linterBundleSettings?.overrides?.react?.['react/forbid-component-props']?.allowStyleFor)
98
+ allowedFor: ensureType.array(config.ts?.overrides?.react?.['react/forbid-component-props']?.allowStyleFor)
98
99
  }
99
100
  ] }],
100
101
  'react/forbid-dom-props': 'error',
@@ -28,7 +28,7 @@ module.exports = {
28
28
  *
29
29
  * @see https://github.com/sindresorhus/eslint-plugin-unicorn
30
30
  */
31
- 'unicorn/filename-case': ['error', {
31
+ 'unicorn/filename-case': ['off', { // Disabled in favour of the linter-bundle `files` task
32
32
  cases: {
33
33
  kebabCase: true,
34
34
  pascalCase: true
@@ -6,7 +6,7 @@ Rendering different output, depending on whether it is SSR or CSR, can lead to h
6
6
 
7
7
  Examples of **incorrect** code for this rule:
8
8
 
9
- ```ts
9
+ ```js
10
10
  if ('undefined' === typeof window) {}
11
11
  if ('undefined' == typeof window) {}
12
12
  if (typeof window !== 'undefined') {}
@@ -6,6 +6,8 @@ const path = require('node:path');
6
6
 
7
7
  const micromatch = require('micromatch');
8
8
 
9
+ const config = require('../../helper/config.js');
10
+
9
11
  /**
10
12
  * @type {import('eslint').Rule.RuleModule}
11
13
  */
@@ -41,7 +43,8 @@ module.exports = {
41
43
  type: 'string'
42
44
  }
43
45
  }
44
- }
46
+ },
47
+ required: ['basePath']
45
48
  }
46
49
  ]
47
50
  }
@@ -49,7 +52,8 @@ module.exports = {
49
52
  create: (context) => {
50
53
  const filePath = context.getFilename();
51
54
  /** @type {{ basePath: string, allowed?: string[]; disallowed?: string[]; }[]} */
52
- const options = context.options;
55
+ // eslint-disable-next-line n/no-process-env -- Only merge the linter-bundle config, if the linting is not started by the linter-bundle CLI tool (e.g. if ESlint is running separately in VSCode), to get warnings shown there too
56
+ const options = (!process.env['LINTER_BUNDLE'] && config.files?.restrictions ? [...config.files.restrictions, ...context.options] : context.options);
53
57
 
54
58
  for (const { basePath, allowed, disallowed } of options) {
55
59
  const normalizedName = path.relative(path.join(process.cwd(), basePath), filePath);
@@ -6,7 +6,7 @@ In projects in which several developers or even teams work together, it is impor
6
6
 
7
7
  Example configuration for this rule:
8
8
 
9
- ```ts
9
+ ```js
10
10
  'restricted-filenames': ['error', {
11
11
  basePath: './src',
12
12
  allowed: [
@@ -48,15 +48,17 @@ If only `disallowed` is set, all unspecified files are allowed.
48
48
 
49
49
  If both are set, `disallowed` wins over `allowed` and all unspecified files are disallowed.
50
50
 
51
- ## Glob patterns for different casings
51
+ ## Useful reusable code snippets in Glob patterns
52
52
 
53
53
  | Name | Example | Glob pattern |
54
54
  |-|-|-|
55
- Snake case | number_of_donuts | `[a-z]*(*([a-z0-9])_+([a-z0-9]))` |
56
- Screaming snake case | NUMBER_OF_DONUTS | `[A-Z]*(*([A-Z0-9])_+([A-Z0-9]))` |
57
- Kebab case | number-of-donuts | `[a-z]*(*([a-z0-9])-+([a-z0-9]))` |
58
- Camel case | numberOfDonuts | `[a-z]*([a-zA-Z0-9])` |
59
- Pascal case | NumberOfDonuts | `[A-Z]*([a-zA-Z0-9])` |
55
+ | Lower case | DONUTS | `[a-z]*([a-z0-9])` |
56
+ | Upper case | donuts | `[A-Z]*([A-Z0-9])` |
57
+ | Snake case | number_of_donuts | `[a-z]*(*([a-z0-9]-)+([a-z0-9]))` |
58
+ | Screaming snake case | NUMBER_OF_DONUTS | `[A-Z]*(*([A-Z0-9]_)+([A-Z0-9]))` |
59
+ | Kebab case | number-of-donuts | `[a-z]*(*([a-z0-9]-)+([a-z0-9]))` |
60
+ | Camel case | numberOfDonuts | `[a-z]*([a-zA-Z0-9])` |
61
+ | Pascal case | NumberOfDonuts | `[A-Z]*([a-zA-Z0-9])` |
60
62
 
61
63
  ## Glob pattern templates
62
64
 
@@ -64,13 +66,15 @@ Instead of defining the same complex patterns over and over again, e.g. for casi
64
66
 
65
67
  Example of `.eslintrc.js`:
66
68
 
67
- ```ts
68
- const casing = {
69
- snake: '[a-z]*(*([a-z0-9])_+([a-z0-9]))',
70
- screamingSnake: '[A-Z]*(*([A-Z0-9])_+([A-Z0-9]))',
71
- kebab: '[a-z]*(*([a-z0-9])-+([a-z0-9]))',
72
- camel: '[a-z]*([a-zA-Z0-9])',
73
- pascal: '[A-Z]*([a-zA-Z0-9])'
69
+ ```js
70
+ const snippets = {
71
+ lowerCase: '[a-z]*([a-z0-9])',
72
+ upperCase: '[A-Z]*([A-Z0-9])',
73
+ snakeCase: '[a-z]*(*([a-z0-9]_)+([a-z0-9]))',
74
+ screamingSnakeCase: '[A-Z]*(*([A-Z0-9]_)+([A-Z0-9]))',
75
+ kebabCase: '[a-z]*(*([a-z0-9]-)+([a-z0-9]))',
76
+ camelCase: '[a-z]*([a-zA-Z0-9])',
77
+ pascalCase: '[A-Z]*([a-zA-Z0-9])'
74
78
  };
75
79
 
76
80
  module.exports = {
@@ -78,8 +82,8 @@ module.exports = {
78
82
  'restricted-filenames': ['error', {
79
83
  basePath: './src',
80
84
  allowed: [
81
- `components/${casing.pascal}/index.tsx`,
82
- `utils/{index.ts,lib/${casing.camel}?(.spec).ts}`
85
+ `components/${snippets.pascalCase}/index.tsx`,
86
+ `utils/{index.ts,lib/${snippets.camelCase}?(.spec).ts}`
83
87
  ]
84
88
  }]
85
89
  }
package/files/index.js ADDED
@@ -0,0 +1,100 @@
1
+ /**
2
+ * @file Ensures that only files which match given glob patterns are part of the project.
3
+ */
4
+
5
+ const fs = require('node:fs');
6
+ const path = require('node:path');
7
+
8
+ const micromatch = require('micromatch');
9
+
10
+ const config = require('../helper/config.js');
11
+
12
+ const restrictions = config.files?.restrictions;
13
+
14
+ if (Array.isArray(restrictions)) {
15
+ let noOfErrors = 0;
16
+
17
+ const argFiles = process.argv.splice(2).map((filePath) => path.join(process.cwd(), filePath));
18
+
19
+ /**
20
+ * Reports the error to the `console` and increase the error counter.
21
+ *
22
+ * @param {string} filePath - Path to the file which is not allowed
23
+ * @returns {void}
24
+ */
25
+ const report = (filePath) => {
26
+ process.stderr.write(`Disallowed filename "${filePath}"\n`);
27
+
28
+ noOfErrors++;
29
+ };
30
+
31
+ for (const { basePath, allowed, disallowed } of restrictions) {
32
+ const absoluteBasePath = path.join(process.cwd(), basePath);
33
+
34
+ const files = (argFiles.length > 0 ? argFiles.filter((filePath) => !path.relative(absoluteBasePath, filePath).startsWith('..')) : getFiles(absoluteBasePath));
35
+
36
+ for (const filePath of files) {
37
+ const normalizedFilePath = path.relative(absoluteBasePath, filePath);
38
+
39
+ if (allowed && !disallowed) {
40
+ if (!micromatch.isMatch(normalizedFilePath, allowed, { dot: true })) {
41
+ report(normalizedFilePath);
42
+
43
+ continue;
44
+ }
45
+ }
46
+ else if (!allowed && disallowed) {
47
+ if (micromatch.isMatch(normalizedFilePath, disallowed, { dot: true })) {
48
+ report(normalizedFilePath);
49
+
50
+ continue;
51
+ }
52
+ }
53
+ else if (allowed && disallowed) {
54
+ if (
55
+ micromatch.isMatch(normalizedFilePath, disallowed, { dot: true }) ||
56
+ !micromatch.isMatch(normalizedFilePath, allowed, { dot: true })
57
+ ) {
58
+ report(normalizedFilePath);
59
+
60
+ continue;
61
+ }
62
+ }
63
+ }
64
+ }
65
+
66
+ if (noOfErrors > 0) {
67
+ process.stderr.write(`\nFound ${noOfErrors} disallowed file${(noOfErrors !== 0 ? 's' : '')}\n`);
68
+
69
+ process.exitCode = -1;
70
+ }
71
+ }
72
+ else {
73
+ process.stderr.write('No file restrictions found\n');
74
+
75
+ process.exitCode = -1;
76
+ }
77
+
78
+ /**
79
+ * Returns a list of all files within a given directory.
80
+ *
81
+ * @param {string} directory - Base directory for the file search
82
+ * @param {string[]} [files] - List of files which are already found (is this parameter unset for the initial call)
83
+ * @returns {string[]} List of files which have been found
84
+ */
85
+ function getFiles (directory, files = []) {
86
+ const fileList = fs.readdirSync(directory);
87
+
88
+ for (const file of fileList) {
89
+ const name = `${directory}/${file}`;
90
+
91
+ if (fs.statSync(name).isDirectory()) {
92
+ getFiles(name, files);
93
+ }
94
+ else {
95
+ files.push(name);
96
+ }
97
+ }
98
+
99
+ return files;
100
+ }
@@ -0,0 +1,48 @@
1
+ /**
2
+ * @file Returns the `.linter-bundle.js` configuration result.
3
+ */
4
+
5
+ const fs = require('node:fs');
6
+ const path = require('node:path');
7
+
8
+ /**
9
+ * @typedef linterBundleConfig
10
+ * @property {boolean} [verbose]
11
+ * @property {boolean} [timing]
12
+ * @property {boolean} [git]
13
+ * @property {{ tsconfig?: string; }} [tsc]
14
+ * @property {{ tsconfig?: string; include?: string[]; exclude?: string[]; overrides?: {
15
+ * general?: {
16
+ * 'no-restricted-globals'?: { additionalRestrictions?: { name: string; message: string; }[]; },
17
+ * 'no-restricted-properties'?: { additionalRestrictions?: { object: string; property: string; message: string; }[]; },
18
+ * 'no-restricted-syntax'?: { additionalRestrictions?: { selector: string; message: string; }[]; },
19
+ * 'import/order'?: { additionalExternalPatterns?: string[]; }
20
+ * },
21
+ * react?: {
22
+ * 'react/forbid-component-props'?: { allowClassNameFor?: string[]; allowStyleFor?: string[]; }
23
+ * }
24
+ * }; }} [ts]
25
+ * @property {{ patternPrefix?: string; }} [sass]
26
+ * @property {{ minSeverity?: 'info' | 'low' | 'moderate' | 'high' | 'critical'; exclude?: string[]; }} [audit]
27
+ * @property {{ restrictions: { basePath: string; allowed?: string[]; disallowed?: string[]; }[]; }} [files]
28
+ */
29
+
30
+ module.exports = (
31
+ loadConfig('.linter-bundle.json') ??
32
+ loadConfig('.linter-bundle.cjs') ??
33
+ loadConfig('.linter-bundle.js') ??
34
+ {}
35
+ );
36
+
37
+ /**
38
+ * Load a config file if it exist.
39
+ *
40
+ * @param {string} fileName - The name of the config file
41
+ * @returns {linterBundleConfig | undefined} - Either the file content for `undefined` if the file does not exist.
42
+ * */
43
+ function loadConfig (fileName) {
44
+ const filePath = path.join(process.cwd(), fileName);
45
+
46
+ // eslint-disable-next-line import/no-dynamic-require -- Required here to load the configuration file.
47
+ return (fs.existsSync(filePath) ? require(filePath) : undefined);
48
+ }
@@ -0,0 +1,52 @@
1
+ /**
2
+ * @file Handling of changed files from Git.
3
+ */
4
+
5
+ const { runProcess } = require('./run-process.js');
6
+
7
+ /** @typedef {import('./run-process').ProcessResult} ProcessResult */
8
+
9
+ /** @type {{ diff: Promise<ProcessResult>; modified: Promise<ProcessResult>; deleted: Promise<ProcessResult>; } | undefined} */
10
+ let gitFilesProcessPromise;
11
+ /** @type {string[] | undefined} */
12
+ let gitFiles;
13
+
14
+ /**
15
+ * Returns a list of changed files based on Git.
16
+ *
17
+ * @public
18
+ * @returns {Promise<string[]>} The list of changed files.
19
+ */
20
+ async function getGitFiles () {
21
+ if (!gitFilesProcessPromise) {
22
+ gitFilesProcessPromise = {
23
+ // Returns changed files, also stashed and committed
24
+ diff: runProcess('git diff --name-only -z @{upstream}'),
25
+ // Returns unstashed files (including deleted)
26
+ modified: runProcess('git ls-files -o -m --exclude-standard --full-name --deduplicate -z'),
27
+ // Returns unstashed, deleted files - @todo Is there a way to also get a list of deleted stashed/committed files?
28
+ deleted: runProcess('git ls-files -d --exclude-standard --full-name --deduplicate -z')
29
+ };
30
+ }
31
+
32
+ const gitProcessResult = {
33
+ diff: await gitFilesProcessPromise.diff,
34
+ modified: await gitFilesProcessPromise.modified,
35
+ deleted: await gitFilesProcessPromise.deleted
36
+ };
37
+
38
+ if (!gitFiles) {
39
+ const deletedFiles = gitProcessResult.deleted.stdout.trim().split('\0');
40
+
41
+ gitFiles = [
42
+ ...gitProcessResult.diff.stdout.trim().split('\0'),
43
+ ...gitProcessResult.modified.stdout.trim().split('\0')
44
+ ].filter((file, index, self) => !deletedFiles.includes(file) && self.indexOf(file) === index);
45
+ }
46
+
47
+ return gitFiles;
48
+ }
49
+
50
+ module.exports = {
51
+ getGitFiles
52
+ };
@@ -25,7 +25,8 @@ async function runProcess (command, options) {
25
25
  /** @type {string[]} */
26
26
  const stderr = [];
27
27
 
28
- const lintingProcess = childProcess.exec(command, { ...options, shell: os.userInfo().shell });
28
+ // eslint-disable-next-line n/no-process-env -- We need to access `process.env`, because this is the default value if `env` is not set.
29
+ const lintingProcess = childProcess.exec(command, { ...options, env: { ...process.env, ...options?.env, LINTER_BUNDLE: '1' }, shell: os.userInfo().shell });
29
30
 
30
31
  lintingProcess.stdout?.on('data', (/** @type {string} */data) => {
31
32
  stdout.push(data);