@open-xchange/linter-presets 0.0.2 → 0.0.4
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 +18 -3
- package/README.md +21 -8
- package/lib/eslint/README.md +83 -0
- package/lib/eslint/config/base.js +0 -1
- package/lib/eslint/config/imports.js +68 -0
- package/lib/eslint/config/js.js +1 -1
- package/lib/eslint/config/ts.js +1 -1
- package/lib/eslint/env/browser.d.ts +10 -2
- package/lib/eslint/env/browser.js +44 -30
- package/lib/eslint/env/browser.md +33 -0
- package/lib/eslint/env/codecept.d.ts +2 -1
- package/lib/eslint/env/codecept.md +35 -0
- package/lib/eslint/env/eslint.d.ts +14 -0
- package/lib/eslint/env/eslint.js +41 -0
- package/lib/eslint/env/eslint.md +32 -0
- package/lib/eslint/env/jest.d.ts +2 -1
- package/lib/eslint/env/jest.md +32 -0
- package/lib/eslint/env/node.d.ts +8 -1
- package/lib/eslint/env/node.js +10 -6
- package/lib/eslint/env/node.md +34 -0
- package/lib/eslint/env/project.d.ts +21 -0
- package/lib/eslint/env/{plugin.js → project.js} +23 -22
- package/lib/eslint/env/project.md +129 -0
- package/lib/eslint/env/react.d.ts +2 -1
- package/lib/eslint/env/react.md +39 -0
- package/lib/eslint/env/tsconfig.d.ts +2 -1
- package/lib/eslint/env/tsconfig.js +0 -2
- package/lib/eslint/env/tsconfig.md +35 -0
- package/lib/eslint/env/vitest.d.ts +2 -1
- package/lib/eslint/env/vitest.md +36 -0
- package/lib/eslint/index.d.ts +6 -4
- package/lib/eslint/index.js +9 -3
- package/lib/eslint/rules/no-amd-module-directive.js +57 -0
- package/lib/eslint/rules/no-invalid-modules.js +205 -0
- package/lib/eslint/shared/env-utils.d.ts +97 -0
- package/lib/eslint/shared/env-utils.js +105 -0
- package/lib/eslint/shared/rule-utils.js +162 -0
- package/lib/eslint/shared/types.d.ts +1 -25
- package/lib/index.d.ts +1 -1
- package/{doc/stylelint.md → lib/stylelint/README.md} +10 -3
- package/lib/stylelint/index.d.ts +2 -2
- package/lib/stylelint/index.js +42 -12
- package/lib/stylelint/types.d.ts +1 -1
- package/package.json +89 -69
- package/doc/eslint.md +0 -343
- package/lib/eslint/env/plugin.d.ts +0 -13
- package/lib/eslint/shared/constants.js +0 -33
- /package/{doc/utils.md → lib/utils/README.md} +0 -0
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* @copyright Copyright (c) Open-Xchange GmbH, Germany <info@open-xchange.com>
|
|
3
|
+
* @license AGPL-3.0
|
|
4
|
+
*
|
|
5
|
+
* This code is free software: you can redistribute it and/or modify
|
|
6
|
+
* it under the terms of the GNU Affero General Public License as published by
|
|
7
|
+
* the Free Software Foundation, either version 3 of the License, or
|
|
8
|
+
* (at your option) any later version.
|
|
9
|
+
*
|
|
10
|
+
* This program is distributed in the hope that it will be useful,
|
|
11
|
+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
12
|
+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
13
|
+
* GNU Affero General Public License for more details.
|
|
14
|
+
*
|
|
15
|
+
* You should have received a copy of the GNU Affero General Public License
|
|
16
|
+
* along with OX App Suite. If not, see <https://www.gnu.org/licenses/agpl-3.0.txt>.
|
|
17
|
+
*
|
|
18
|
+
* Any use of the work other than as authorized under this license or copyright law is prohibited.
|
|
19
|
+
*
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
import { createRequire } from "node:module";
|
|
23
|
+
import { posix, dirname, extname } from "node:path";
|
|
24
|
+
|
|
25
|
+
import { packageUpSync } from "package-up";
|
|
26
|
+
|
|
27
|
+
import { Schema, makeArray, toPosixPath, isFile, matchModuleName, getModuleName } from "../shared/rule-utils.js";
|
|
28
|
+
|
|
29
|
+
// constants ==================================================================
|
|
30
|
+
|
|
31
|
+
const FILE_EXTENSIONS = ["js", "ts", "d.ts"];
|
|
32
|
+
|
|
33
|
+
// exports ====================================================================
|
|
34
|
+
|
|
35
|
+
/** @type {import("eslint").Rule.RuleModule} */
|
|
36
|
+
export default {
|
|
37
|
+
|
|
38
|
+
meta: {
|
|
39
|
+
type: "problem",
|
|
40
|
+
schema: [
|
|
41
|
+
// single options object
|
|
42
|
+
Schema.options({
|
|
43
|
+
alias: Schema.dictionary(Schema.string()), // alias paths may be empty
|
|
44
|
+
external: Schema.maybeArray(Schema.stringNE()),
|
|
45
|
+
packages: Schema.dictionary(Schema.options({
|
|
46
|
+
src: Schema.maybeArray(Schema.stringNE()),
|
|
47
|
+
dependsOn: Schema.maybeArray(Schema.stringNE()),
|
|
48
|
+
optional: Schema.boolean(),
|
|
49
|
+
}, ["src"])),
|
|
50
|
+
}),
|
|
51
|
+
],
|
|
52
|
+
messages: {
|
|
53
|
+
SOURCE_FILE_NOT_FOUND: "Source file for '{{moduleName}}' not found.",
|
|
54
|
+
UNEXPECTED_OPTIONAL_STATIC: "Unexpected static import of optional package.",
|
|
55
|
+
INVALID_PACKAGE_HIERARCHY: "Low-level package cannot import modules from higher-level package.",
|
|
56
|
+
},
|
|
57
|
+
fixable: "code",
|
|
58
|
+
},
|
|
59
|
+
|
|
60
|
+
create(context) {
|
|
61
|
+
|
|
62
|
+
// resolve and decompose rule settings
|
|
63
|
+
const {
|
|
64
|
+
alias = {},
|
|
65
|
+
external: externalModules = [],
|
|
66
|
+
packages = {},
|
|
67
|
+
} = context.options[0] || {};
|
|
68
|
+
|
|
69
|
+
// convert "alias" option to map
|
|
70
|
+
const aliasMap = new Map(Object.entries(alias));
|
|
71
|
+
|
|
72
|
+
// convert "packages" option (strings to arrays)
|
|
73
|
+
const packagesMap = new Map();
|
|
74
|
+
const packagesGlobs = [];
|
|
75
|
+
for (const [key, settings] of Object.entries(packages)) {
|
|
76
|
+
const { src, dependsOn, ...rest } = settings;
|
|
77
|
+
const srcGlobs = makeArray(src);
|
|
78
|
+
packagesMap.set(key, { src: srcGlobs, dependsOn: makeArray(dependsOn), ...rest });
|
|
79
|
+
packagesGlobs.push(...srcGlobs);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// resolve file name
|
|
83
|
+
const packagePath = packageUpSync();
|
|
84
|
+
const rootDir = toPosixPath(dirname(packagePath));
|
|
85
|
+
const fileName = toPosixPath(context.filename);
|
|
86
|
+
if (!fileName.startsWith(rootDir + "/")) {
|
|
87
|
+
throw new Error("invalid root directory");
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// path of current module (slice rootDir with "/" from start, and extension with "." from end)
|
|
91
|
+
const fileExt = extname(fileName);
|
|
92
|
+
const selfModulePath = fileName.slice(rootDir.length + 1, -fileExt.length);
|
|
93
|
+
if (!selfModulePath) {
|
|
94
|
+
throw new Error("invalid own module path");
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// replace alias path with alias key
|
|
98
|
+
let selfModuleName = selfModulePath;
|
|
99
|
+
for (const [aliasKey, aliasPath] of aliasMap) {
|
|
100
|
+
if (selfModulePath.startsWith(aliasPath + "/")) {
|
|
101
|
+
selfModuleName = aliasKey + "/" + selfModulePath.slice(aliasPath.length + 1);
|
|
102
|
+
break;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// returns an existing alias key used by the passed module name
|
|
107
|
+
const resolveAlias = moduleName => {
|
|
108
|
+
const [key, ...rest] = moduleName.split("/");
|
|
109
|
+
const aliasPath = (key && rest[0]) ? aliasMap.get(key) : undefined;
|
|
110
|
+
const aliasKey = aliasPath ? key : "";
|
|
111
|
+
const modulePath = aliasPath ? posix.join(aliasPath, ...rest) : moduleName;
|
|
112
|
+
return { aliasKey, modulePath };
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
// returns whether a source file exists for the specified module
|
|
116
|
+
const fileExists = resolvedName => {
|
|
117
|
+
// check modules with explicit extension
|
|
118
|
+
const resolvedPath = posix.join(rootDir, resolvedName);
|
|
119
|
+
if (extname(resolvedName)) { return isFile(resolvedPath); }
|
|
120
|
+
// search for a file with a known extension
|
|
121
|
+
return FILE_EXTENSIONS.some(ext => isFile(resolvedPath + "." + ext));
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
// returns whether the passed module name is an installed NPM package
|
|
125
|
+
const requireModule = createRequire(packagePath);
|
|
126
|
+
const packageExists = moduleName => {
|
|
127
|
+
try {
|
|
128
|
+
requireModule.resolve(moduleName);
|
|
129
|
+
return true;
|
|
130
|
+
} catch {
|
|
131
|
+
return false;
|
|
132
|
+
}
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
return {
|
|
136
|
+
"ImportDeclaration, TSExternalModuleReference, ImportExpression, ExportAllDeclaration, ExportNamedDeclaration"(node) {
|
|
137
|
+
|
|
138
|
+
// import/export statement must contain string literal
|
|
139
|
+
const { sourceNode, moduleName } = getModuleName(node);
|
|
140
|
+
if (!sourceNode || !moduleName) { return; }
|
|
141
|
+
|
|
142
|
+
// skip glob patterns in TypeScript type imports
|
|
143
|
+
if ((node.type === "ImportDeclaration") && (node.importKind === "type") && moduleName.includes("*")) {
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// extract alias key, replace with alias path
|
|
148
|
+
const { aliasKey, modulePath } = resolveAlias(moduleName);
|
|
149
|
+
|
|
150
|
+
// whether the import is a known external module
|
|
151
|
+
const isExternal = !aliasKey && matchModuleName(moduleName, externalModules);
|
|
152
|
+
|
|
153
|
+
// whether the import is an installed NPM package
|
|
154
|
+
const isPackage = !isExternal && !aliasKey && packageExists(moduleName);
|
|
155
|
+
|
|
156
|
+
// check existence of source file
|
|
157
|
+
if (!isExternal && !isPackage && !fileExists(modulePath)) {
|
|
158
|
+
context.report({ messageId: "SOURCE_FILE_NOT_FOUND", node: sourceNode, data: { moduleName } });
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// check dependencies if the imported module is covered in the configuration
|
|
163
|
+
if (!matchModuleName(moduleName, packagesGlobs)) { return; }
|
|
164
|
+
|
|
165
|
+
// check package dependencies
|
|
166
|
+
// whether an invalid static import has been found
|
|
167
|
+
let invalidStatic = false;
|
|
168
|
+
|
|
169
|
+
// recursively checks the imported module for dependency violations
|
|
170
|
+
const checkDependencies = (settings, root) => {
|
|
171
|
+
|
|
172
|
+
// only allow static imports from specified package, if it is not optional
|
|
173
|
+
if (matchModuleName(moduleName, settings.src)) {
|
|
174
|
+
if (!root && (node.type !== "ImportExpression") && settings.optional) { invalidStatic = true; }
|
|
175
|
+
return true;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// do not traverse into dependencies of optional modules
|
|
179
|
+
if ((settings.dependsOn.length === 0) || (!root && settings.optional)) {
|
|
180
|
+
return false;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// allow imports from any of the dependent packages
|
|
184
|
+
return settings.dependsOn.some(key => checkDependencies(packagesMap.get(key), false));
|
|
185
|
+
};
|
|
186
|
+
|
|
187
|
+
// check dependencies of all configured packages (not for anonymous modules)
|
|
188
|
+
let invalidDeps = false;
|
|
189
|
+
for (const settings of packagesMap.values()) {
|
|
190
|
+
if (matchModuleName(selfModuleName, settings.src) && !checkDependencies(settings, true)) {
|
|
191
|
+
invalidDeps = true;
|
|
192
|
+
break;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// report package hierarchy errors
|
|
197
|
+
if (invalidStatic) {
|
|
198
|
+
context.report({ messageId: "UNEXPECTED_OPTIONAL_STATIC", node: sourceNode });
|
|
199
|
+
} else if (invalidDeps) {
|
|
200
|
+
context.report({ messageId: "INVALID_PACKAGE_HIERARCHY", node: sourceNode });
|
|
201
|
+
}
|
|
202
|
+
},
|
|
203
|
+
};
|
|
204
|
+
},
|
|
205
|
+
};
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
|
|
2
|
+
import { Linter } from "eslint";
|
|
3
|
+
|
|
4
|
+
import { FlatConfigArray } from "./types";
|
|
5
|
+
|
|
6
|
+
// types ======================================================================
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Shared options for an environment preset with included and excluded files.
|
|
10
|
+
*/
|
|
11
|
+
export interface EnvFilesOptions {
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Glob patterns for source files to be included into the environment.
|
|
15
|
+
*/
|
|
16
|
+
files: readonly string[];
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Glob patterns for source files matching `files` to be ignored.
|
|
20
|
+
*/
|
|
21
|
+
ignores?: readonly string[];
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// ----------------------------------------------------------------------------
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Shared options for an environment preset: include and exclude files, and add
|
|
28
|
+
* more linter rules.
|
|
29
|
+
*/
|
|
30
|
+
export interface EnvBaseOptions extends EnvFilesOptions {
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Additional linter rules to be added to the configuration.
|
|
34
|
+
*/
|
|
35
|
+
rules?: Linter.RulesRecord;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// ----------------------------------------------------------------------------
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Configuration for a single banned global or import.
|
|
42
|
+
*/
|
|
43
|
+
export interface EnvRestrictedName {
|
|
44
|
+
name: string;
|
|
45
|
+
message: string;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Configuration for a single banned object property.
|
|
50
|
+
*/
|
|
51
|
+
export interface EnvRestrictedProperty {
|
|
52
|
+
object: string;
|
|
53
|
+
property: string;
|
|
54
|
+
message: string;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Configuration for a single banned syntax construct.
|
|
59
|
+
*/
|
|
60
|
+
export interface EnvRestrictedSyntax {
|
|
61
|
+
selector: string;
|
|
62
|
+
message: string;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Collection of banned globals, imports, properties, and syntax constructs.
|
|
67
|
+
*/
|
|
68
|
+
export interface EnvRestrictedItems {
|
|
69
|
+
/** The global symbols to be banned. */
|
|
70
|
+
globals?: readonly EnvRestrictedName[];
|
|
71
|
+
/** The module imports to be banned. */
|
|
72
|
+
imports?: readonly EnvRestrictedName[];
|
|
73
|
+
/** The global object properties to be banned. */
|
|
74
|
+
properties?: readonly EnvRestrictedProperty[];
|
|
75
|
+
/** The syntax constructs to be banned. */
|
|
76
|
+
syntax?: readonly EnvRestrictedSyntax[];
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Collection of banned globals, imports, properties, and syntax constructs,
|
|
81
|
+
* for a specific subset of the files included in an environment.
|
|
82
|
+
*/
|
|
83
|
+
export interface EnvRestrictedOverride extends EnvFilesOptions, EnvRestrictedItems {
|
|
84
|
+
merge?: boolean;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Configuration options for restricted imports and globals.
|
|
89
|
+
*/
|
|
90
|
+
export interface EnvRestrictedOptions extends EnvRestrictedItems {
|
|
91
|
+
overrides?: readonly EnvRestrictedOverride[];
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export interface EnvRestrictedRulesResult {
|
|
95
|
+
rules: Linter.RulesRecord;
|
|
96
|
+
overrides: FlatConfigArray;
|
|
97
|
+
}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
|
|
2
|
+
// constants ==================================================================
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Global symbols to be banned in all source code files.
|
|
6
|
+
*/
|
|
7
|
+
const RESTRICTED_GLOBALS = [
|
|
8
|
+
{ name: "isFinite", message: "Use 'Number.isFinite' instead." },
|
|
9
|
+
{ name: "isNaN", message: "Use 'Number.isNaN' instead." },
|
|
10
|
+
];
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Syntax constructs to be banned in all source code files.
|
|
14
|
+
*/
|
|
15
|
+
const RESTRICTED_SYNTAX = [
|
|
16
|
+
{ selector: ":matches(PropertyDefinition, MethodDefinition[kind!='constructor'])[accessibility='public']", message: "Remove 'public' keyword." },
|
|
17
|
+
{ selector: ":matches(PropertyDefinition, MethodDefinition[kind!='constructor'])[accessibility='private'][decorators.length=0]", message: "Use #private syntax instead." },
|
|
18
|
+
{ selector: "MethodDefinition[kind='constructor'] TSParameterProperty[accessibility]", message: "Use explicit class properties." },
|
|
19
|
+
];
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Shared options for the core rule `no-unused-vars`, and the plugin rule
|
|
23
|
+
* `@typescript-eslint/no-unused-vars`.
|
|
24
|
+
*/
|
|
25
|
+
export const NO_UNUSED_VARS_OPTIONS = {
|
|
26
|
+
varsIgnorePattern: "^_",
|
|
27
|
+
argsIgnorePattern: "^_",
|
|
28
|
+
destructuredArrayIgnorePattern: "^_",
|
|
29
|
+
caughtErrors: "all",
|
|
30
|
+
ignoreRestSiblings: true,
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
// functions ==================================================================
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Concatenates the elements of multiple arrays. Skips all falsy parameters.
|
|
37
|
+
*
|
|
38
|
+
* @template T
|
|
39
|
+
*
|
|
40
|
+
* @param {Array<T[] | undefined | null | false>} arrays
|
|
41
|
+
* The arrays to be concatenated.
|
|
42
|
+
*
|
|
43
|
+
* @returns {T[]}
|
|
44
|
+
* The concatenated arrays.
|
|
45
|
+
*/
|
|
46
|
+
function flatten(...arrays) {
|
|
47
|
+
return arrays.flatMap(array => array || []);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Creates a rules record for all restricted items.
|
|
52
|
+
*
|
|
53
|
+
* @param {(key: string) => object[]} generator
|
|
54
|
+
* The generator callback function that returns the restricted items to be
|
|
55
|
+
* inserted into the linter rules.
|
|
56
|
+
*
|
|
57
|
+
* @returns {import("eslint").Linter.RulesRecord}
|
|
58
|
+
* The rules record containing entries for all existing restricted items.
|
|
59
|
+
*/
|
|
60
|
+
function createRulesRecord(generator) {
|
|
61
|
+
const rules = {};
|
|
62
|
+
for (const key of ["globals", "imports", "properties", "syntax"]) {
|
|
63
|
+
const items = generator(key);
|
|
64
|
+
if (items?.length) { rules[`no-restricted-${key}`] = ["error", ...items]; }
|
|
65
|
+
}
|
|
66
|
+
return rules;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Generates various "no-restricted-?" rules from the passed configuration.
|
|
71
|
+
*
|
|
72
|
+
* @param {import("./env-utils").EnvRestrictedItems} fixed
|
|
73
|
+
* The fixed restricted items provided by the environment preset.
|
|
74
|
+
*
|
|
75
|
+
* @param {import("./env-utils").EnvRestrictedOptions} [options]
|
|
76
|
+
* The custom configuration passed to an environment preset.
|
|
77
|
+
*
|
|
78
|
+
* @returns {import("./env-utils").EnvRestrictedRulesResult}
|
|
79
|
+
* The generated linter rules to forbid the restricted items.
|
|
80
|
+
*/
|
|
81
|
+
export function generateRestrictedRules(fixed, options) {
|
|
82
|
+
|
|
83
|
+
// restricted items for all files in the environment
|
|
84
|
+
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
|
+
};
|
|
90
|
+
|
|
91
|
+
// base rules for all files in the environment
|
|
92
|
+
const rules = createRulesRecord(key => items[key]);
|
|
93
|
+
|
|
94
|
+
// generate the override entries (join with base items if specified)
|
|
95
|
+
const overrides = [];
|
|
96
|
+
for (const override of options?.overrides ?? []) {
|
|
97
|
+
overrides.push({
|
|
98
|
+
files: override.files,
|
|
99
|
+
ignores: override.ignores ?? [],
|
|
100
|
+
rules: createRulesRecord(key => flatten(override.join && items[key], override[key])),
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return { rules, overrides };
|
|
105
|
+
}
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* @copyright Copyright (c) Open-Xchange GmbH, Germany <info@open-xchange.com>
|
|
3
|
+
* @license AGPL-3.0
|
|
4
|
+
*
|
|
5
|
+
* This code is free software: you can redistribute it and/or modify
|
|
6
|
+
* it under the terms of the GNU Affero General Public License as published by
|
|
7
|
+
* the Free Software Foundation, either version 3 of the License, or
|
|
8
|
+
* (at your option) any later version.
|
|
9
|
+
*
|
|
10
|
+
* This program is distributed in the hope that it will be useful,
|
|
11
|
+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
12
|
+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
13
|
+
* GNU Affero General Public License for more details.
|
|
14
|
+
*
|
|
15
|
+
* You should have received a copy of the GNU Affero General Public License
|
|
16
|
+
* along with OX App Suite. If not, see <https://www.gnu.org/licenses/agpl-3.0.txt>.
|
|
17
|
+
*
|
|
18
|
+
* Any use of the work other than as authorized under this license or copyright law is prohibited.
|
|
19
|
+
*
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
import { posix, sep } from "node:path";
|
|
23
|
+
import { lstatSync } from "node:fs";
|
|
24
|
+
|
|
25
|
+
import pm from "picomatch";
|
|
26
|
+
|
|
27
|
+
// Schema =====================================================================
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Helper functions to build a JSON schema for custom ESLint rules.
|
|
31
|
+
*/
|
|
32
|
+
export const Schema = {
|
|
33
|
+
|
|
34
|
+
boolean() {
|
|
35
|
+
return { type: "boolean" };
|
|
36
|
+
},
|
|
37
|
+
|
|
38
|
+
string(options) {
|
|
39
|
+
return { type: "string", ...options };
|
|
40
|
+
},
|
|
41
|
+
|
|
42
|
+
stringNE(options) {
|
|
43
|
+
return Schema.string({ minLength: 1, ...options });
|
|
44
|
+
},
|
|
45
|
+
|
|
46
|
+
array(items) {
|
|
47
|
+
return { type: "array", items, uniqueItems: true };
|
|
48
|
+
},
|
|
49
|
+
|
|
50
|
+
maybeArray(items) {
|
|
51
|
+
return { oneOf: [items, Schema.array(items)] };
|
|
52
|
+
},
|
|
53
|
+
|
|
54
|
+
dictionary(items) {
|
|
55
|
+
return { type: "object", additionalProperties: items };
|
|
56
|
+
},
|
|
57
|
+
|
|
58
|
+
options(properties, required) {
|
|
59
|
+
return { type: "object", properties, required, additionalProperties: false };
|
|
60
|
+
},
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
// functions ==================================================================
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Returns whether the passed value is nullish (either `undefined` or `null`).
|
|
67
|
+
*
|
|
68
|
+
* @param {unknown} value
|
|
69
|
+
* The value to be checked.
|
|
70
|
+
*
|
|
71
|
+
* @returns {boolean}
|
|
72
|
+
* Whether the passed value is nullish (either `undefined` or `null`).
|
|
73
|
+
*/
|
|
74
|
+
export function isNullish(value) {
|
|
75
|
+
return (value === undefined) || (value === null);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Returns a passed array unmodified, converts nullish values to an empty
|
|
80
|
+
* array, and converts all other values to an array with one element.
|
|
81
|
+
*
|
|
82
|
+
* @param {unknown} value
|
|
83
|
+
* Any value to be converted to an array.
|
|
84
|
+
*
|
|
85
|
+
* @returns {unknown[]}
|
|
86
|
+
* An array containing the value, unless the value is already an array.
|
|
87
|
+
*/
|
|
88
|
+
export function makeArray(value) {
|
|
89
|
+
return Array.isArray(value) ? value : isNullish(value) ? [] : [value];
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Converts a Windows file path to a Posix file path.
|
|
94
|
+
*
|
|
95
|
+
* @param {string} path
|
|
96
|
+
* The file path to be normalized to Posix format.
|
|
97
|
+
*
|
|
98
|
+
* @returns {string}
|
|
99
|
+
* The normalized Posix file path.
|
|
100
|
+
*/
|
|
101
|
+
export function toPosixPath(path) {
|
|
102
|
+
return path.replaceAll(sep, posix.sep);
|
|
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
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Returns whether a module name matches the specified glob patterns.
|
|
125
|
+
*
|
|
126
|
+
* @param {string} moduleName
|
|
127
|
+
* The module name to be checked.
|
|
128
|
+
*
|
|
129
|
+
* @param {import("picomatch").Glob} patterns
|
|
130
|
+
* The glob patterns to be matched against the module name.
|
|
131
|
+
*
|
|
132
|
+
* @returns {boolean}
|
|
133
|
+
* Whether the module name matches at least one glob pattern.
|
|
134
|
+
*/
|
|
135
|
+
export const matchModuleName = pm.isMatch;
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Extracts the source node and module name of an import/export statement node,
|
|
139
|
+
* or a dynamic import expression node.
|
|
140
|
+
*
|
|
141
|
+
* @typedef {import("acorn").Node} Node
|
|
142
|
+
*
|
|
143
|
+
* @param {Node} node
|
|
144
|
+
* The import/export statement node, or dynamic import expression node.
|
|
145
|
+
*
|
|
146
|
+
* @returns {{ sourceNode: Node | undefined; moduleName: string }}
|
|
147
|
+
* The string literal node containing the module name, and the resulting
|
|
148
|
+
* extracted module name.
|
|
149
|
+
*/
|
|
150
|
+
export function getModuleName(node) {
|
|
151
|
+
|
|
152
|
+
// TSExternalModuleReference: module name in property "expression", otherwise "source"
|
|
153
|
+
const sourceNode = (node.type === "TSExternalModuleReference") ? node.expression : node.source;
|
|
154
|
+
|
|
155
|
+
// module name is expected to be a string literal
|
|
156
|
+
const isStringLiteral = (sourceNode?.type === "Literal") && (typeof sourceNode.value === "string");
|
|
157
|
+
|
|
158
|
+
// strip URL query strings from module name
|
|
159
|
+
const moduleName = isStringLiteral ? sourceNode.value.replace(/\?.*$/, "") : "";
|
|
160
|
+
|
|
161
|
+
return { sourceNode, moduleName };
|
|
162
|
+
}
|
|
@@ -76,7 +76,7 @@ export interface StylisticOptions {
|
|
|
76
76
|
/**
|
|
77
77
|
* Configuration options for linting the entire project with ESLint.
|
|
78
78
|
*/
|
|
79
|
-
export interface
|
|
79
|
+
export interface ConfigureOptions {
|
|
80
80
|
|
|
81
81
|
/**
|
|
82
82
|
* Glob patterns with all files and folders to be ignored by ESLint.
|
|
@@ -105,27 +105,3 @@ export interface ESLintConfigureOptions {
|
|
|
105
105
|
*/
|
|
106
106
|
rules?: Linter.RulesRecord;
|
|
107
107
|
}
|
|
108
|
-
|
|
109
|
-
// ----------------------------------------------------------------------------
|
|
110
|
-
|
|
111
|
-
/**
|
|
112
|
-
* Shared options for an environment preset: include and exclude files, and add
|
|
113
|
-
* more linter rules.
|
|
114
|
-
*/
|
|
115
|
-
export interface EnvBaseOptions {
|
|
116
|
-
|
|
117
|
-
/**
|
|
118
|
-
* Glob patterns for source files to be included into the environment.
|
|
119
|
-
*/
|
|
120
|
-
files: readonly string[];
|
|
121
|
-
|
|
122
|
-
/**
|
|
123
|
-
* Glob patterns for source files matching `files` to be ignored.
|
|
124
|
-
*/
|
|
125
|
-
ignores?: readonly string[];
|
|
126
|
-
|
|
127
|
-
/**
|
|
128
|
-
* Additional linter rules to be added to the configuration.
|
|
129
|
-
*/
|
|
130
|
-
rules?: Linter.RulesRecord;
|
|
131
|
-
}
|
package/lib/index.d.ts
CHANGED
|
@@ -8,9 +8,16 @@ The module `stylelint` provides a `configure` function for project-wide StyleLin
|
|
|
8
8
|
|
|
9
9
|
Generates standard configuration targeting the source files in the entire project. Internally, various StyleLint plugins will be included and configured to use their recommended rule sets.
|
|
10
10
|
|
|
11
|
+
| Target | StyleLint Plugin |
|
|
12
|
+
| - | - |
|
|
13
|
+
| Stylesheet files (`.css`, `.less`, `.scss`) | StyleLint core rules |
|
|
14
|
+
| Less files only | [stylelint-config-standard-less](https://github.com/stylelint-less/stylelint-config-standard-less) |
|
|
15
|
+
| Sass files only | [stylelint-config-standard-scss](https://github.com/stylelint-scss/stylelint-config-standard-scss) |
|
|
16
|
+
| Source code formatting | [@stylistic/stylelint-plugin](https://github.com/stylelint-stylistic/stylelint-stylistic) |
|
|
17
|
+
|
|
11
18
|
#### `configure` Signature
|
|
12
19
|
|
|
13
|
-
`function configure(options?:
|
|
20
|
+
`function configure(options?: ConfigureOptions): Linter.FlatConfig[]`
|
|
14
21
|
|
|
15
22
|
#### `configure` Options
|
|
16
23
|
|
|
@@ -19,9 +26,9 @@ Generates standard configuration targeting the source files in the entire projec
|
|
|
19
26
|
| `ignores` | `string[]` | `[]` | Glob patterns with all files and folders to be ignored by StyleLint. |
|
|
20
27
|
| `license` | `string` | `""` | Full path to the template file containing the license header to be used in all source files. |
|
|
21
28
|
| `stylistic` | `StylisticOptions` | | Configuration options for code style. |
|
|
22
|
-
| `stylistic.indent` | `number` | `2` | Default indentation size (number of space characters).
|
|
29
|
+
| `stylistic.indent` | `number` | `2` | Default indentation size (number of space characters). |
|
|
23
30
|
| `stylistic.quotes` | `"single"\|"double"\|"off"` | `"single"` | The type of the string delimiter character. |
|
|
24
|
-
| `rules` | `
|
|
31
|
+
| `rules` | `StyleLint.Config["rules"]` | `{}` | Additional linter rules to be added to the global configuration. |
|
|
25
32
|
|
|
26
33
|
#### `configure` Example
|
|
27
34
|
|
package/lib/stylelint/index.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
|
|
2
2
|
import { Config } from "stylelint";
|
|
3
3
|
|
|
4
|
-
import {
|
|
4
|
+
import { ConfigureOptions } from "./types";
|
|
5
5
|
|
|
6
6
|
// functions ==================================================================
|
|
7
7
|
|
|
@@ -15,4 +15,4 @@ import { StyleLintConfigureOptions } from "./types";
|
|
|
15
15
|
* @returns
|
|
16
16
|
* An array of configuration objects to be added to the flat configuration.
|
|
17
17
|
*/
|
|
18
|
-
export function configure(config?:
|
|
18
|
+
export function configure(config?: ConfigureOptions): Config;
|