@open-xchange/linter-presets 0.0.3 → 0.0.5

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.
package/CHANGELOG.md CHANGED
@@ -18,4 +18,13 @@
18
18
  - added: [ESLint] environment `env.project` (wraps all `env-project/*` rules)
19
19
  - changed: [ESLint] environment `env.plugin` renamed to `env.eslint`
20
20
  - fixed: [StyleLint] support for LESS files
21
- - chore: update dependencies
21
+ - chore: bump dependencies
22
+
23
+ ## [0.0.4] - 2024-07-10
24
+
25
+ - fixed: [ESLint] file exists detection in rule `env-project/no-invalid-modules`
26
+
27
+ ## [0.0.5] - 2024-07-10
28
+
29
+ - chore: [ESLint] documentation for option `restricted`
30
+ - chore: bump dependencies
@@ -14,9 +14,26 @@ function browser(options: EnvBrowserOptions): Linter.FlatConfig[]
14
14
  | - | - | - | - |
15
15
  | `files` | `string[]` | _required_ | Glob patterns for source files to be included. |
16
16
  | `ignores` | `string[]` | `[]` | Glob patterns for source files matching `files` to be ignored. |
17
- | `restricted` | `EnvRestrictedOptions` | `{}` | Settings for banned globals, imports, object properties, and syntax constructs. |
17
+ | `restricted` | `EnvRestrictedOptions` | `{}` | Settings for banned globals, imports, object properties, and syntax constructs (see below). |
18
18
  | `rules` | `Linter.RulesRecord` | `{}` | Additional linter rules to be added to the configuration. |
19
19
 
20
+ ### Option `restricted`
21
+
22
+ - Type: `EnvRestrictedOptions`
23
+ - Default: `{}`
24
+
25
+ This option allows to specify banned globals, imports, object properties, and syntax constructs. These restricted items will be set on top of the built-in restricted items of the environment preset, and will be passed to the ESLint core rules [`no-restricted-globals`](https://eslint.org/docs/latest/rules/no-restricted-globals), [`no-restricted-imports`](https://eslint.org/docs/latest/rules/no-restricted-imports), [`no-restricted-properties`](https://eslint.org/docs/latest/rules/no-restricted-properties), and [`no-restricted-syntax`](https://eslint.org/docs/latest/rules/no-restricted-syntax), respectively.
26
+
27
+ The option `restricted` is an object with the following properties:
28
+
29
+ | Name | Type | Default | Description |
30
+ | - | - | - | - |
31
+ | `globals` | `EnvRestrictedName[]` | `[]` | Banned global symbols. Each entry is an object with string properties `name` and `message`. |
32
+ | `imports` | `EnvRestrictedName[]` | `[]` | Banned modules that must not be imported. Each entry is an object with string properties `name` and `message`. |
33
+ | `properties` | `EnvRestrictedProperty[]` | `[]` | Banned object properties. Each entry is an object with string properties `object`, `property`, and `message`). |
34
+ | `syntax` | `EnvRestrictedSyntax[]` | `[]` | Banned syntax constructs by [AST selectors](https://eslint.org/docs/latest/extend/selectors). Each entry is an object with string properties `selector` and `message`. |
35
+ | `overrides` | `EnvRestrictedOverride[]` | `[]` | Overrides for specific subsets of files in the environment. Each entry is an object with the common properties `files` (required) and `ignores`, and the properties `globals`, `imports`, `properties`, and `syntax` to specify restricted items (see above). All restricted items of overrides will be merged with the common restricted items defined for the entire environment. |
36
+
20
37
  ## Example
21
38
 
22
39
  ```js
@@ -27,6 +44,28 @@ export default [
27
44
  ...eslint.configure({ /* ... */ }),
28
45
  ...eslint.env.browser({
29
46
  files: ["src/**/*.{js,ts}"],
47
+ restricted: {
48
+ globals: [
49
+ { name: "$", message: "Explicitly import the 'jquery' package." },
50
+ ],
51
+ imports: [
52
+ { name: "underscore", message: "Use the 'lodash' package." },
53
+ ],
54
+ properties: [
55
+ { object: "_", property: "flatten", message: "Use native 'Array::flat' instead." },
56
+ ],
57
+ syntax: [
58
+ { selector: "ClassBody > StaticBlock", message: "Static blocks not supported in all browsers." },
59
+ ],
60
+ overrides: [
61
+ {
62
+ files: "src/**/*.ts",
63
+ properties: [
64
+ { object: "$", property: "Deferred", message: "Use native promises in TypeScript code." },
65
+ ],
66
+ },
67
+ ],
68
+ },
30
69
  rules: { /* ... */ },
31
70
  }),
32
71
  ]
@@ -15,9 +15,16 @@ function node(options: EnvNodeOptions): Linter.FlatConfig[]
15
15
  | `files` | `string[]` | _required_ | Glob patterns for source files to be included. |
16
16
  | `ignores` | `string[]` | `[]` | Glob patterns for source files matching `files` to be ignored. |
17
17
  | `sourceType` | `"module"\|"commonjs"` | `"module"` | Specifies how to treat `.js`, `.jsx`, `.ts`, and `.tsx`. |
18
- | `restricted` | `EnvRestrictedOptions` | `{}` | Settings for banned globals, imports, object properties, and syntax constructs. |
18
+ | `restricted` | `EnvRestrictedOptions` | `{}` | Settings for banned globals, imports, object properties, and syntax constructs (see below). |
19
19
  | `rules` | `Linter.RulesRecord` | `{}` | Additional linter rules to be added to the configuration. |
20
20
 
21
+ ### Option `restricted`
22
+
23
+ - Type: `EnvRestrictedOptions`
24
+ - Default: `{}`
25
+
26
+ This option allows to specify banned globals, imports, object properties, and syntax constructs. See [documentation in `env.browser`](./browser.md#option-restricted) for a detailed description.
27
+
21
28
  ## Example
22
29
 
23
30
  ```js
@@ -90,7 +90,7 @@ Each key in the `packages` record is the arbitrary unique name of a package. The
90
90
  | Name | Type | Default | Description |
91
91
  | - | - | - | - |
92
92
  | `src` | `string\|string[]` | _required_ | Glob patterns selecting all source files that are part of the package. |
93
- | `dependsOn` | `string\|string[]` | `[]` | Specifies the names of all packages (dictionary keys of the option `packages`) this package depends on. |
93
+ | `extends` | `string\|string[]` | `[]` | Specifies the names of all packages (dictionary keys of the option `packages`) this package depends on. |
94
94
  | `optional` | `boolean` | `false` | Set to `true` to mark an optional package that may be missing in an installation. Such a package cannot be imported statically (with `import` statement) from a non-optional package, but can only be loaded dynamically at runtime (by calling `import()`). The rule will mark all static imports of optional code as an error. |
95
95
 
96
96
  Modules that are part of such a package may import any modules from the same package, or from packages it depends on (also recursively from their dependent packages).
@@ -112,12 +112,12 @@ export default [
112
112
  },
113
113
  debug: {
114
114
  src: "@/debug/**/*",
115
- dependsOn: "base",
115
+ extends: "base",
116
116
  optional: true,
117
117
  },
118
118
  special: {
119
119
  src: "@/my/special/**/*",
120
- dependsOn: ["base", "debug"],
120
+ extends: ["base", "debug"],
121
121
  },
122
122
  },
123
123
  }),
@@ -21,11 +21,10 @@
21
21
 
22
22
  import { createRequire } from "node:module";
23
23
  import { posix, dirname, extname } from "node:path";
24
- import { existsSync } from "node:fs";
25
24
 
26
25
  import { packageUpSync } from "package-up";
27
26
 
28
- import { Schema, makeArray, toPosixPath, matchModuleName, getModuleName } from "../shared/rule-utils.js";
27
+ import { Schema, makeArray, toPosixPath, isFile, matchModuleName, getModuleName } from "../shared/rule-utils.js";
29
28
 
30
29
  // constants ==================================================================
31
30
 
@@ -45,7 +44,7 @@ export default {
45
44
  external: Schema.maybeArray(Schema.stringNE()),
46
45
  packages: Schema.dictionary(Schema.options({
47
46
  src: Schema.maybeArray(Schema.stringNE()),
48
- dependsOn: Schema.maybeArray(Schema.stringNE()),
47
+ extends: Schema.maybeArray(Schema.stringNE()),
49
48
  optional: Schema.boolean(),
50
49
  }, ["src"])),
51
50
  }),
@@ -70,16 +69,19 @@ export default {
70
69
  // convert "alias" option to map
71
70
  const aliasMap = new Map(Object.entries(alias));
72
71
 
73
- // convert "packages" option (strings to arrays)
72
+ // convert "packages" options (strings to arrays)
74
73
  const packagesMap = new Map();
75
- const packagesGlobs = [];
76
74
  for (const [key, settings] of Object.entries(packages)) {
77
- const { src, dependsOn, ...rest } = settings;
78
- const srcGlobs = makeArray(src);
79
- packagesMap.set(key, { src: srcGlobs, dependsOn: makeArray(dependsOn), ...rest });
80
- packagesGlobs.push(...srcGlobs);
75
+ packagesMap.set(key, {
76
+ ...settings,
77
+ src: makeArray(settings.src),
78
+ extends: makeArray(settings.extends),
79
+ });
81
80
  }
82
81
 
82
+ // collect all globs for package members
83
+ const packagesGlobs = Array.from(packagesMap, settings => settings.src).flat();
84
+
83
85
  // resolve file name
84
86
  const packagePath = packageUpSync();
85
87
  const rootDir = toPosixPath(dirname(packagePath));
@@ -106,10 +108,10 @@ export default {
106
108
 
107
109
  // returns an existing alias key used by the passed module name
108
110
  const resolveAlias = moduleName => {
109
- const [key, rest] = moduleName.split("/", 2);
110
- const aliasPath = (key && rest) ? aliasMap.get(key) : undefined;
111
+ const [key, ...rest] = moduleName.split("/");
112
+ const aliasPath = (key && rest[0]) ? aliasMap.get(key) : undefined;
111
113
  const aliasKey = aliasPath ? key : "";
112
- const modulePath = aliasPath ? posix.join(aliasPath, rest) : moduleName;
114
+ const modulePath = aliasPath ? posix.join(aliasPath, ...rest) : moduleName;
113
115
  return { aliasKey, modulePath };
114
116
  };
115
117
 
@@ -117,9 +119,9 @@ export default {
117
119
  const fileExists = resolvedName => {
118
120
  // check modules with explicit extension
119
121
  const resolvedPath = posix.join(rootDir, resolvedName);
120
- if (extname(resolvedName)) { return existsSync(resolvedPath); }
122
+ if (extname(resolvedName)) { return isFile(resolvedPath); }
121
123
  // search for a file with a known extension
122
- return FILE_EXTENSIONS.some(ext => existsSync(resolvedPath + "." + ext));
124
+ return FILE_EXTENSIONS.some(ext => isFile(resolvedPath + "." + ext));
123
125
  };
124
126
 
125
127
  // returns whether the passed module name is an installed NPM package
@@ -177,12 +179,12 @@ export default {
177
179
  }
178
180
 
179
181
  // do not traverse into dependencies of optional modules
180
- if ((settings.dependsOn.length === 0) || (!root && settings.optional)) {
182
+ if (!settings.extends.length || (!root && settings.optional)) {
181
183
  return false;
182
184
  }
183
185
 
184
186
  // allow imports from any of the dependent packages
185
- return settings.dependsOn.some(key => checkDependencies(packagesMap.get(key), false));
187
+ return settings.extends.some(key => checkDependencies(packagesMap.get(key), false));
186
188
  };
187
189
 
188
190
  // check dependencies of all configured packages (not for anonymous modules)
@@ -80,9 +80,7 @@ export interface EnvRestrictedItems {
80
80
  * Collection of banned globals, imports, properties, and syntax constructs,
81
81
  * for a specific subset of the files included in an environment.
82
82
  */
83
- export interface EnvRestrictedOverride extends EnvFilesOptions, EnvRestrictedItems {
84
- merge?: boolean;
85
- }
83
+ export interface EnvRestrictedOverride extends EnvFilesOptions, EnvRestrictedItems { }
86
84
 
87
85
  /**
88
86
  * Configuration options for restricted imports and globals.
@@ -33,17 +33,17 @@ export const NO_UNUSED_VARS_OPTIONS = {
33
33
  // functions ==================================================================
34
34
 
35
35
  /**
36
- * Concatenates the elements of multiple arrays. Skips all falsy parameters.
36
+ * Concatenates the elements of multiple arrays. Skips all nullish parameters.
37
37
  *
38
38
  * @template T
39
39
  *
40
- * @param {Array<T[] | undefined | null | false>} arrays
40
+ * @param {Array<T[] | undefined | null>} arrays
41
41
  * The arrays to be concatenated.
42
42
  *
43
43
  * @returns {T[]}
44
44
  * The concatenated arrays.
45
45
  */
46
- function flatten(...arrays) {
46
+ function concatArrays(...arrays) {
47
47
  return arrays.flatMap(array => array || []);
48
48
  }
49
49
 
@@ -63,6 +63,10 @@ function createRulesRecord(generator) {
63
63
  const items = generator(key);
64
64
  if (items?.length) { rules[`no-restricted-${key}`] = ["error", ...items]; }
65
65
  }
66
+ // same restrictions for CommonJS `require` as for `import` statements
67
+ if ("no-restricted-imports" in rules) {
68
+ rules["no-restricted-modules"] = rules["no-restricted-imports"];
69
+ }
66
70
  return rules;
67
71
  }
68
72
 
@@ -82,22 +86,22 @@ export function generateRestrictedRules(fixed, options) {
82
86
 
83
87
  // restricted items for all files in the environment
84
88
  const items = {
85
- globals: flatten(RESTRICTED_GLOBALS, fixed.globals, options?.globals),
86
- imports: flatten(fixed.imports, options?.imports),
87
- properties: flatten(fixed.properties, options?.properties),
88
- syntax: flatten(RESTRICTED_SYNTAX, fixed.syntax, options?.syntax),
89
+ globals: concatArrays(RESTRICTED_GLOBALS, fixed.globals, options?.globals),
90
+ imports: concatArrays(fixed.imports, options?.imports),
91
+ properties: concatArrays(fixed.properties, options?.properties),
92
+ syntax: concatArrays(RESTRICTED_SYNTAX, fixed.syntax, options?.syntax),
89
93
  };
90
94
 
91
95
  // base rules for all files in the environment
92
96
  const rules = createRulesRecord(key => items[key]);
93
97
 
94
- // generate the override entries (join with base items if specified)
98
+ // generate the override entries (join with base items)
95
99
  const overrides = [];
96
100
  for (const override of options?.overrides ?? []) {
97
101
  overrides.push({
98
102
  files: override.files,
99
103
  ignores: override.ignores ?? [],
100
- rules: createRulesRecord(key => flatten(override.join && items[key], override[key])),
104
+ rules: createRulesRecord(key => concatArrays(items[key], override[key])),
101
105
  });
102
106
  }
103
107
 
@@ -20,6 +20,7 @@
20
20
  */
21
21
 
22
22
  import { posix, sep } from "node:path";
23
+ import { lstatSync } from "node:fs";
23
24
 
24
25
  import pm from "picomatch";
25
26
 
@@ -101,6 +102,24 @@ export function toPosixPath(path) {
101
102
  return path.replaceAll(sep, posix.sep);
102
103
  }
103
104
 
105
+ /**
106
+ * Returns whether the passed path refers to an existing file.
107
+ *
108
+ * @param {string} path
109
+ * The path to be checked.
110
+ *
111
+ * @returns {boolean}
112
+ * Whether the passed path refers to an existing file (returns `false` for
113
+ * existing directories).
114
+ */
115
+ export function isFile(path) {
116
+ try {
117
+ return lstatSync(path).isFile();
118
+ } catch {
119
+ return false;
120
+ }
121
+ }
122
+
104
123
  /**
105
124
  * Returns whether a module name matches the specified glob patterns.
106
125
  *
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@open-xchange/linter-presets",
3
- "version": "0.0.3",
3
+ "version": "0.0.5",
4
4
  "description": "Configuration presets for ESLint and StyleLint",
5
5
  "repository": "https://gitlab.open-xchange.com/fspd/npm-packages/linter-presets",
6
6
  "license": "MIT",
@@ -32,7 +32,7 @@
32
32
  "eslint-plugin-import": "2.29.1",
33
33
  "eslint-plugin-jest": "28.6.0",
34
34
  "eslint-plugin-jest-dom": "5.4.0",
35
- "eslint-plugin-jsdoc": "48.5.2",
35
+ "eslint-plugin-jsdoc": "48.6.0",
36
36
  "eslint-plugin-jsonc": "2.16.0",
37
37
  "eslint-plugin-jsx-a11y": "6.9.0",
38
38
  "eslint-plugin-jsx-expressions": "1.3.2",
@@ -42,7 +42,7 @@
42
42
  "eslint-plugin-react": "7.34.3",
43
43
  "eslint-plugin-react-hooks": "4.6.2",
44
44
  "eslint-plugin-react-hooks-static-deps": "1.0.7",
45
- "eslint-plugin-react-refresh": "0.4.7",
45
+ "eslint-plugin-react-refresh": "0.4.8",
46
46
  "eslint-plugin-testing-library": "6.2.2",
47
47
  "eslint-plugin-vitest": "0.5.4",
48
48
  "eslint-plugin-yml": "1.14.0",