@isentinel/eslint-config 6.0.0-beta.43 → 6.0.0-beta.45
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/README.md +4 -0
- package/dist/cli.mjs +1 -1
- package/dist/index.mjs +170 -34
- package/dist/lint-cli.mjs +1 -1
- package/dist/oxlint.d.mts +3 -2
- package/dist/oxlint.mjs +111 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -928,6 +928,10 @@ are `{nodeName}`, `{NodeName}`, `{node_name}` and `{NODE_NAME}`. A file already
|
|
|
928
928
|
matching a template is exempt from it. A source file with no test disables the
|
|
929
929
|
rule at the source.
|
|
930
930
|
|
|
931
|
+
The rule needs no type information, so oxlint runs it too: the same option is
|
|
932
|
+
accepted by the oxlint factory, and hybrid mode (`oxlint: true`) hands the check
|
|
933
|
+
to oxlint.
|
|
934
|
+
|
|
931
935
|
Two caveats. Deleting `foo.spec.ts` does not change `foo.ts`, so a cached run
|
|
932
936
|
replays the old clean result — the check is only sound uncached. And the plugin
|
|
933
937
|
writes `projectStructure.cache.json` into `projectRoot` while it has something
|
package/dist/cli.mjs
CHANGED
|
@@ -9,7 +9,7 @@ import path from "node:path";
|
|
|
9
9
|
import { existsSync, readFileSync } from "fs";
|
|
10
10
|
import { execSync } from "node:child_process";
|
|
11
11
|
//#region package.json
|
|
12
|
-
var version = "6.0.0-beta.
|
|
12
|
+
var version = "6.0.0-beta.45";
|
|
13
13
|
var package_default = {
|
|
14
14
|
name: "@isentinel/eslint-config",
|
|
15
15
|
version,
|
package/dist/index.mjs
CHANGED
|
@@ -3932,6 +3932,7 @@ async function pnpm(options) {
|
|
|
3932
3932
|
"pnpm/yaml-enforce-settings": ["error", { settings: {
|
|
3933
3933
|
catalogMode: "prefer",
|
|
3934
3934
|
cleanupUnusedCatalogs: true,
|
|
3935
|
+
minimumReleaseAgeExcludePrune: true,
|
|
3935
3936
|
shellEmulator: true,
|
|
3936
3937
|
trustPolicy: "no-downgrade",
|
|
3937
3938
|
trustPolicyIgnoreAfter: 10080
|
|
@@ -3950,42 +3951,44 @@ async function detectCatalogUsage() {
|
|
|
3950
3951
|
return yaml.includes("catalog:") || yaml.includes("catalogs:");
|
|
3951
3952
|
}
|
|
3952
3953
|
//#endregion
|
|
3953
|
-
//#region src/
|
|
3954
|
+
//#region src/rules/project-structure.ts
|
|
3954
3955
|
init_globs();
|
|
3955
3956
|
/** Stands for the extension of the file a rule matched. */
|
|
3956
3957
|
const EXTENSION_TOKEN = "{ext}";
|
|
3957
3958
|
const DEFAULT_ENFORCE_EXISTENCE = [`{node-name}.spec.${EXTENSION_TOKEN}`];
|
|
3958
3959
|
/** Reusable-rule id for "a folder of any name". */
|
|
3959
3960
|
const ANY_FOLDER = "isentinelAnyFolder";
|
|
3960
|
-
|
|
3961
|
+
/** The files the co-location check runs on, before `ignores`. */
|
|
3962
|
+
const PROJECT_STRUCTURE_FILES = [GLOB_SRC];
|
|
3963
|
+
/** The files the co-location check skips. */
|
|
3964
|
+
const PROJECT_STRUCTURE_IGNORES = [
|
|
3961
3965
|
...GLOB_TESTS,
|
|
3962
3966
|
GLOB_DTS,
|
|
3963
3967
|
...GLOB_BUILD_CONFIGS
|
|
3964
|
-
]
|
|
3965
|
-
|
|
3966
|
-
|
|
3968
|
+
];
|
|
3969
|
+
/**
|
|
3970
|
+
* Co-location rules shared between the ESLint and oxlint factories.
|
|
3971
|
+
*
|
|
3972
|
+
* `folder-structure` is not type-aware and runs unchanged as an oxlint
|
|
3973
|
+
* jsPlugin, so both engines have to hand it the same options or they disagree
|
|
3974
|
+
* about which files are co-located.
|
|
3975
|
+
*
|
|
3976
|
+
* @param options - The templates to enforce, and the roots they resolve
|
|
3977
|
+
* against.
|
|
3978
|
+
* @returns The rule map.
|
|
3979
|
+
*/
|
|
3980
|
+
function folderStructureRules({ enforceExistence = DEFAULT_ENFORCE_EXISTENCE, projectRoot = process.cwd(), structureRoot } = {}) {
|
|
3967
3981
|
const children = buildChildren(typeof enforceExistence === "string" ? [enforceExistence] : enforceExistence);
|
|
3968
|
-
return [{
|
|
3969
|
-
|
|
3970
|
-
|
|
3971
|
-
|
|
3972
|
-
|
|
3973
|
-
|
|
3974
|
-
|
|
3975
|
-
|
|
3976
|
-
|
|
3977
|
-
|
|
3978
|
-
projectRoot,
|
|
3979
|
-
rules: { [ANY_FOLDER]: {
|
|
3980
|
-
name: "*",
|
|
3981
|
-
children
|
|
3982
|
-
} },
|
|
3983
|
-
structure: children,
|
|
3984
|
-
...structureRoot === void 0 ? {} : { structureRoot }
|
|
3985
|
-
}],
|
|
3986
|
-
...overrides
|
|
3987
|
-
}
|
|
3988
|
-
}];
|
|
3982
|
+
return { "project-structure/folder-structure": ["error", {
|
|
3983
|
+
longPathsInfo: false,
|
|
3984
|
+
projectRoot,
|
|
3985
|
+
rules: { [ANY_FOLDER]: {
|
|
3986
|
+
name: "*",
|
|
3987
|
+
children
|
|
3988
|
+
} },
|
|
3989
|
+
structure: children,
|
|
3990
|
+
...structureRoot === void 0 ? {} : { structureRoot }
|
|
3991
|
+
}] };
|
|
3989
3992
|
}
|
|
3990
3993
|
/**
|
|
3991
3994
|
* The rules matching the files inside one folder, in the order
|
|
@@ -4012,6 +4015,28 @@ function buildChildren(templates) {
|
|
|
4012
4015
|
return children;
|
|
4013
4016
|
}
|
|
4014
4017
|
//#endregion
|
|
4018
|
+
//#region src/eslint/configs/project-structure.ts
|
|
4019
|
+
async function projectStructure({ enforceExistence, files = PROJECT_STRUCTURE_FILES, ignores = PROJECT_STRUCTURE_IGNORES, overrides = {}, projectRoot, structureRoot } = {}) {
|
|
4020
|
+
await ensurePackages(["eslint-plugin-project-structure"]);
|
|
4021
|
+
const { projectStructurePlugin } = await import("eslint-plugin-project-structure");
|
|
4022
|
+
return [{
|
|
4023
|
+
name: "isentinel/project-structure/setup",
|
|
4024
|
+
plugins: { "project-structure": projectStructurePlugin }
|
|
4025
|
+
}, {
|
|
4026
|
+
name: "isentinel/project-structure/rules",
|
|
4027
|
+
files,
|
|
4028
|
+
ignores,
|
|
4029
|
+
rules: {
|
|
4030
|
+
...folderStructureRules({
|
|
4031
|
+
enforceExistence,
|
|
4032
|
+
projectRoot,
|
|
4033
|
+
structureRoot
|
|
4034
|
+
}),
|
|
4035
|
+
...overrides
|
|
4036
|
+
}
|
|
4037
|
+
}];
|
|
4038
|
+
}
|
|
4039
|
+
//#endregion
|
|
4015
4040
|
//#region src/rules/promise.ts
|
|
4016
4041
|
/**
|
|
4017
4042
|
* Promise rules shared between the ESLint and oxlint factories.
|
|
@@ -4883,6 +4908,7 @@ function sortPnpmWorkspace() {
|
|
|
4883
4908
|
"blockExoticSubdeps",
|
|
4884
4909
|
"cacheDir",
|
|
4885
4910
|
"catalogMode",
|
|
4911
|
+
"catalogPrune",
|
|
4886
4912
|
"cleanupUnusedCatalogs",
|
|
4887
4913
|
"dedupeDirectDeps",
|
|
4888
4914
|
"dedupePeerDependents",
|
|
@@ -4892,6 +4918,7 @@ function sortPnpmWorkspace() {
|
|
|
4892
4918
|
"enablePrePostScripts",
|
|
4893
4919
|
"engineStrict",
|
|
4894
4920
|
"extendNodePath",
|
|
4921
|
+
"globalShims",
|
|
4895
4922
|
"hoist",
|
|
4896
4923
|
"hoistPattern",
|
|
4897
4924
|
"hoistWorkspacePackages",
|
|
@@ -4907,6 +4934,7 @@ function sortPnpmWorkspace() {
|
|
|
4907
4934
|
"managePackageManagerVersions",
|
|
4908
4935
|
"minimumReleaseAge",
|
|
4909
4936
|
"minimumReleaseAgeExclude",
|
|
4937
|
+
"minimumReleaseAgeExcludePrune",
|
|
4910
4938
|
"minimumReleaseAgeIgnoreMissingTime",
|
|
4911
4939
|
"minimumReleaseAgeStrict",
|
|
4912
4940
|
"modulesDir",
|
|
@@ -4929,6 +4957,7 @@ function sortPnpmWorkspace() {
|
|
|
4929
4957
|
"shamefullyHoist",
|
|
4930
4958
|
"sharedWorkspaceLockfile",
|
|
4931
4959
|
"shellEmulator",
|
|
4960
|
+
"sideEffectsCache",
|
|
4932
4961
|
"stateDir",
|
|
4933
4962
|
"strictDepBuilds",
|
|
4934
4963
|
"strictPeerDependencies",
|
|
@@ -4941,7 +4970,8 @@ function sortPnpmWorkspace() {
|
|
|
4941
4970
|
"trustPolicyIgnoreAfter",
|
|
4942
4971
|
"updateConfig",
|
|
4943
4972
|
"updateNotifier",
|
|
4944
|
-
"verifyDepsBeforeRun"
|
|
4973
|
+
"verifyDepsBeforeRun",
|
|
4974
|
+
"virtualStoreType"
|
|
4945
4975
|
],
|
|
4946
4976
|
...[
|
|
4947
4977
|
"gitBranchLockfile",
|
|
@@ -4968,7 +4998,8 @@ function sortPnpmWorkspace() {
|
|
|
4968
4998
|
"onlyBuiltDependencies",
|
|
4969
4999
|
"onlyBuiltDependenciesFile",
|
|
4970
5000
|
"packageExtensions",
|
|
4971
|
-
"peerDependencyRules"
|
|
5001
|
+
"peerDependencyRules",
|
|
5002
|
+
"tasks"
|
|
4972
5003
|
],
|
|
4973
5004
|
{ order: { type: "asc" } }
|
|
4974
5005
|
],
|
|
@@ -4993,7 +5024,7 @@ function sortPnpmWorkspace() {
|
|
|
4993
5024
|
natural: true,
|
|
4994
5025
|
type: "asc"
|
|
4995
5026
|
},
|
|
4996
|
-
pathPattern: "^(supportedArchitectures\\.(cpu|libc|os)|updateConfig\\.ignoreDependencies|peerDependencyRules\\.(allowAny|ignoreMissing)|auditConfig\\.(ignoreCves|ignoreGhsas))$"
|
|
5027
|
+
pathPattern: "^(supportedArchitectures\\.(cpu|libc|os)|update\\.ignoreDeps|updateConfig\\.ignoreDependencies|peerDependencyRules\\.(allowAny|ignoreMissing)|audit\\.ignore|auditConfig\\.(ignoreCves|ignoreGhsas)|sideEffectsCache\\.remote\\.packages|tasks\\.[^.]+\\.dependsOn)$"
|
|
4997
5028
|
}
|
|
4998
5029
|
]
|
|
4999
5030
|
}
|
|
@@ -15660,6 +15691,7 @@ var init_adapters = __esmMin((() => {
|
|
|
15660
15691
|
"oxfmt": "eslint-plugin-oxfmt",
|
|
15661
15692
|
"oxlint-comments": "oxlint-plugin-oxlint-comments",
|
|
15662
15693
|
"perfectionist": "eslint-plugin-perfectionist",
|
|
15694
|
+
"project-structure": "eslint-plugin-project-structure",
|
|
15663
15695
|
"promise-js": "eslint-plugin-promise",
|
|
15664
15696
|
"react-jsx": "eslint-plugin-react-jsx",
|
|
15665
15697
|
"react-naming-convention": "eslint-plugin-react-naming-convention",
|
|
@@ -16549,6 +16581,9 @@ var init_oxlint_capabilities = __esmMin((() => {
|
|
|
16549
16581
|
"prefer-spread",
|
|
16550
16582
|
"prefer-template",
|
|
16551
16583
|
"preserve-caught-error",
|
|
16584
|
+
"project-structure/file-composition",
|
|
16585
|
+
"project-structure/folder-structure",
|
|
16586
|
+
"project-structure/independent-modules",
|
|
16552
16587
|
"promise/always-return",
|
|
16553
16588
|
"promise/avoid-new",
|
|
16554
16589
|
"promise/catch-or-return",
|
|
@@ -19811,8 +19846,10 @@ var oxlint_drop_exports = /* @__PURE__ */ __exportAll$2({
|
|
|
19811
19846
|
* Warn when a user-supplied config references a rule that oxlint owns in hybrid
|
|
19812
19847
|
* mode (`oxlint: true`). The preset drops every oxlint-covered rule from the
|
|
19813
19848
|
* ESLint side and lets oxlint format real JS/TS files, so such entries silently
|
|
19814
|
-
* do nothing.
|
|
19815
|
-
* virtual files, so the preset re-enables those
|
|
19849
|
+
* do nothing. Configs oxlint cannot reach are exempt: Markdown-scoped ones
|
|
19850
|
+
* (oxlint cannot lint Markdown virtual files, so the preset re-enables those
|
|
19851
|
+
* rules there) and ones scoped to a non-JS/TS language, which oxlint never
|
|
19852
|
+
* parses.
|
|
19816
19853
|
*
|
|
19817
19854
|
* @param configs - The resolved flat config items.
|
|
19818
19855
|
* @param mode - The hybrid mode; in `native` mode jsPlugin rules and formatting
|
|
@@ -19842,6 +19879,11 @@ function warnMissingTsgolint() {
|
|
|
19842
19879
|
* sibling is inserted directly after the original so later configs (for
|
|
19843
19880
|
* example the markdown disables) still take precedence.
|
|
19844
19881
|
*
|
|
19882
|
+
* Configs scoped to a language oxlint does not parse are skipped outright: it
|
|
19883
|
+
* reads only the JS/TS family, so handing it `oxfmt/oxfmt` for YAML, JSON, CSS
|
|
19884
|
+
* or GraphQL would drop the formatter from ESLint without any engine picking it
|
|
19885
|
+
* up. See {@link targetsJsOrTs}.
|
|
19886
|
+
*
|
|
19845
19887
|
* @param configs - The resolved flat config items (mutated in place).
|
|
19846
19888
|
* @param typeAware - Whether oxlint runs type-aware (oxlint-tsgolint present).
|
|
19847
19889
|
* When `false`, tsgolint rules are kept in ESLint so they do not vanish from
|
|
@@ -19851,7 +19893,7 @@ function warnMissingTsgolint() {
|
|
|
19851
19893
|
function dropOxlintCoveredRules(configs, typeAware = true, mode = "full") {
|
|
19852
19894
|
for (let index = 0; index < configs.length; index += 1) {
|
|
19853
19895
|
const config = configs[index];
|
|
19854
|
-
if (config?.name === void 0 || !config.name.startsWith("isentinel/") || config.name.endsWith("/markdown-code") || config.rules === void 0 || targetsMarkdown(config.files)) continue;
|
|
19896
|
+
if (config?.name === void 0 || !config.name.startsWith("isentinel/") || config.name.endsWith("/markdown-code") || config.rules === void 0 || targetsMarkdown(config.files) || !targetsJsOrTs(config.files)) continue;
|
|
19855
19897
|
const dropped = dropCoveredRulesFromConfig(config.rules, typeAware, mode);
|
|
19856
19898
|
if (Object.keys(dropped).length === 0) continue;
|
|
19857
19899
|
if ((config.ignores ?? []).some((pattern) => typeof pattern === "string" && pattern.includes(".md/"))) continue;
|
|
@@ -19900,10 +19942,98 @@ function targetsMarkdown(files) {
|
|
|
19900
19942
|
const patterns = files.flat();
|
|
19901
19943
|
return patterns.length > 0 && patterns.every((pattern) => typeof pattern === "string" && pattern.includes(".md"));
|
|
19902
19944
|
}
|
|
19945
|
+
/**
|
|
19946
|
+
* The strings one part of a {@link GLOB_GROUP_SPLIT} split can stand for: the
|
|
19947
|
+
* comma-separated members of a brace group, the individual characters of a
|
|
19948
|
+
* bracket group, or the literal text itself. A part that still holds a group
|
|
19949
|
+
* delimiter is a nested or unterminated group, which is not expanded.
|
|
19950
|
+
*
|
|
19951
|
+
* @param part - One part of a glob fragment split on its groups.
|
|
19952
|
+
* @returns The alternatives, or `undefined` when the part is not expandable.
|
|
19953
|
+
*/
|
|
19954
|
+
function groupAlternatives(part) {
|
|
19955
|
+
const body = part.slice(1, -1);
|
|
19956
|
+
if (part.startsWith("{")) return body.split(",");
|
|
19957
|
+
if (part.startsWith("[")) return body.match(GLOB_ANY_CHARACTER) ?? [];
|
|
19958
|
+
return GLOB_GROUP_DELIMITER.test(part) ? void 0 : [part];
|
|
19959
|
+
}
|
|
19960
|
+
/**
|
|
19961
|
+
* Expand the brace and character-class alternatives of a glob fragment.
|
|
19962
|
+
*
|
|
19963
|
+
* Only the closed forms the preset's globs use are handled (`{,c,m}`, `[jt]`);
|
|
19964
|
+
* anything with a wildcard, a nested group or an unterminated group yields
|
|
19965
|
+
* `undefined`, meaning "could be anything". Callers treat that as a match, so
|
|
19966
|
+
* an unknown pattern keeps the pre-existing drop behavior.
|
|
19967
|
+
*
|
|
19968
|
+
* @param fragment - The glob fragment to expand.
|
|
19969
|
+
* @returns Every literal string the fragment can produce, or `undefined` when
|
|
19970
|
+
* the fragment is not a closed set of literals.
|
|
19971
|
+
*/
|
|
19972
|
+
function expandGlobAlternatives(fragment) {
|
|
19973
|
+
if (GLOB_WILDCARD.test(fragment)) return;
|
|
19974
|
+
let results = [""];
|
|
19975
|
+
for (const part of fragment.split(GLOB_GROUP_SPLIT)) {
|
|
19976
|
+
const alternatives = groupAlternatives(part);
|
|
19977
|
+
if (alternatives === void 0) return;
|
|
19978
|
+
results = results.flatMap((prefix) => {
|
|
19979
|
+
return alternatives.map((alternative) => prefix + alternative);
|
|
19980
|
+
});
|
|
19981
|
+
}
|
|
19982
|
+
return results;
|
|
19983
|
+
}
|
|
19984
|
+
/**
|
|
19985
|
+
* The index of the extension dot in a glob's last path segment, or `-1` when it
|
|
19986
|
+
* has none. Dots inside a brace or bracket group are masked out first: they
|
|
19987
|
+
* belong to an alternative, not to the extension boundary.
|
|
19988
|
+
*
|
|
19989
|
+
* @param segment - The last path segment of a `files` pattern.
|
|
19990
|
+
* @returns The index of the extension dot, or `-1`.
|
|
19991
|
+
*/
|
|
19992
|
+
function extensionDotIndex(segment) {
|
|
19993
|
+
return segment.replaceAll(GLOB_GROUP, (group) => "\0".repeat(group.length)).lastIndexOf(".");
|
|
19994
|
+
}
|
|
19995
|
+
/**
|
|
19996
|
+
* Whether a single `files` pattern can match a file oxlint reads.
|
|
19997
|
+
*
|
|
19998
|
+
* Oxlint parses only the JS/TS family, so a pattern whose extension is a closed
|
|
19999
|
+
* set of non-JS/TS extensions (`**\/*.y{,a}ml`, `**\/*.json{,5,c}`) describes
|
|
20000
|
+
* files oxlint never sees. A pattern whose last segment has no extension, or
|
|
20001
|
+
* whose extension is not a closed set, matches conservatively.
|
|
20002
|
+
*
|
|
20003
|
+
* @param pattern - The `files` pattern to classify.
|
|
20004
|
+
* @returns Whether oxlint can lint something the pattern matches.
|
|
20005
|
+
*/
|
|
20006
|
+
function patternTargetsJsOrTs(pattern) {
|
|
20007
|
+
const segment = pattern.slice(pattern.lastIndexOf("/") + 1);
|
|
20008
|
+
const dot = extensionDotIndex(segment);
|
|
20009
|
+
if (dot === -1) return true;
|
|
20010
|
+
const extensions = expandGlobAlternatives(segment.slice(dot + 1));
|
|
20011
|
+
if (extensions === void 0) return true;
|
|
20012
|
+
return extensions.some((extension) => JS_TS_EXTENSIONS.has(extension));
|
|
20013
|
+
}
|
|
20014
|
+
/**
|
|
20015
|
+
* Whether a config's `files` reach any file oxlint can lint.
|
|
20016
|
+
*
|
|
20017
|
+
* Hybrid mode only hands a rule to oxlint for the files oxlint actually parses.
|
|
20018
|
+
* A config scoped to YAML, JSON, CSS or GraphQL is invisible to oxlint, so its
|
|
20019
|
+
* rules must stay in ESLint or they run in neither engine — the case that
|
|
20020
|
+
* silently dropped `oxfmt/oxfmt` from every non-JS formatting config.
|
|
20021
|
+
*
|
|
20022
|
+
* @param files - The config's `files` patterns; nested arrays are AND-combined,
|
|
20023
|
+
* so they only reach JS/TS when every member does.
|
|
20024
|
+
* @returns Whether oxlint can lint something the config targets.
|
|
20025
|
+
*/
|
|
20026
|
+
function targetsJsOrTs(files) {
|
|
20027
|
+
if (files === void 0 || files.length === 0) return true;
|
|
20028
|
+
return files.some((entry) => {
|
|
20029
|
+
if (typeof entry === "string") return patternTargetsJsOrTs(entry);
|
|
20030
|
+
return entry.every((pattern) => patternTargetsJsOrTs(pattern));
|
|
20031
|
+
});
|
|
20032
|
+
}
|
|
19903
20033
|
function findDeadMappedRules(configs, mode) {
|
|
19904
20034
|
const references = [];
|
|
19905
20035
|
for (const config of configs) {
|
|
19906
|
-
if (config.rules === void 0 || isPresetConfig(config) || targetsMarkdown(config.files)) continue;
|
|
20036
|
+
if (config.rules === void 0 || isPresetConfig(config) || targetsMarkdown(config.files) || !targetsJsOrTs(config.files)) continue;
|
|
19907
20037
|
const label = describeConfig(config);
|
|
19908
20038
|
for (const [rule, value] of Object.entries(config.rules)) if (value !== void 0 && (presetHandsRuleToOxlint(rule, mode) || mode === "full" && HYBRID_FORMATTING_RULES.has(rule))) references.push({
|
|
19909
20039
|
config: label,
|
|
@@ -19964,11 +20094,17 @@ function markdownVirtualFiles(files) {
|
|
|
19964
20094
|
return [GLOB_MARKDOWN_CODE, ...[pattern].flat()];
|
|
19965
20095
|
});
|
|
19966
20096
|
}
|
|
19967
|
-
var HYBRID_FORMATTING_RULES;
|
|
20097
|
+
var HYBRID_FORMATTING_RULES, JS_TS_EXTENSIONS, GLOB_WILDCARD, GLOB_GROUP, GLOB_GROUP_SPLIT, GLOB_GROUP_DELIMITER, GLOB_ANY_CHARACTER;
|
|
19968
20098
|
var init_oxlint_drop = __esmMin((() => {
|
|
19969
20099
|
init_globs();
|
|
19970
20100
|
init_routing();
|
|
19971
20101
|
HYBRID_FORMATTING_RULES = /* @__PURE__ */ new Set(["oxfmt/oxfmt"]);
|
|
20102
|
+
JS_TS_EXTENSIONS = new Set(GLOB_SRC_EXTENSIONS);
|
|
20103
|
+
GLOB_WILDCARD = /[*?]/;
|
|
20104
|
+
GLOB_GROUP = /\{[^{}]*\}|\[[^[\]]*\]/gu;
|
|
20105
|
+
GLOB_GROUP_SPLIT = /(\{[^{}]*\}|\[[^[\]]*\])/u;
|
|
20106
|
+
GLOB_GROUP_DELIMITER = /[{}[\]]/u;
|
|
20107
|
+
GLOB_ANY_CHARACTER = /./gu;
|
|
19972
20108
|
}));
|
|
19973
20109
|
//#endregion
|
|
19974
20110
|
//#region src/eslint/factory.ts
|
package/dist/lint-cli.mjs
CHANGED
|
@@ -38,7 +38,7 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
38
38
|
}) : target, mod));
|
|
39
39
|
//#endregion
|
|
40
40
|
//#region package.json
|
|
41
|
-
var version = "6.0.0-beta.
|
|
41
|
+
var version = "6.0.0-beta.45";
|
|
42
42
|
//#endregion
|
|
43
43
|
//#region src/lint-cli/lib/cli/types.ts
|
|
44
44
|
/**
|
package/dist/oxlint.d.mts
CHANGED
|
@@ -3860,6 +3860,7 @@ interface OverrideSiteChains {
|
|
|
3860
3860
|
e18e: MainChain;
|
|
3861
3861
|
eslintPlugin: FeatureChain;
|
|
3862
3862
|
javascript: MainChain;
|
|
3863
|
+
projectStructure: FeatureChain;
|
|
3863
3864
|
react: ReactChain;
|
|
3864
3865
|
roblox: MainChain;
|
|
3865
3866
|
typescript: MainChain;
|
|
@@ -33259,7 +33260,7 @@ type OxlintCommentsRequireDescription = [] | [{
|
|
|
33259
33260
|
ignore?: ("oxlint-disable" | "oxlint-disable-line" | "oxlint-disable-next-line" | "oxlint-enable")[];
|
|
33260
33261
|
}];
|
|
33261
33262
|
/** JsPlugin prefixes whose rules keep their ESLint-side names in oxlint configs. */
|
|
33262
|
-
type OxlintKeptJsPluginPrefix = '@cspell' | 'antfu' | 'better-max-params' | 'comment-length' | 'de-morgan' | 'e18e' | 'erasable-syntax-only' | 'eslint-plugin' | 'flawless' | 'jest-extended' | 'oxfmt' | 'perfectionist' | 'react-jsx' | 'react-naming-convention' | 'roblox' | 'sentinel' | 'small-rules' | 'sonar' | 'style' | 'testing-library' | 'ts' | 'unused-imports';
|
|
33263
|
+
type OxlintKeptJsPluginPrefix = '@cspell' | 'antfu' | 'better-max-params' | 'comment-length' | 'de-morgan' | 'e18e' | 'erasable-syntax-only' | 'eslint-plugin' | 'flawless' | 'jest-extended' | 'oxfmt' | 'perfectionist' | 'project-structure' | 'react-jsx' | 'react-naming-convention' | 'roblox' | 'sentinel' | 'small-rules' | 'sonar' | 'style' | 'testing-library' | 'ts' | 'unused-imports';
|
|
33263
33264
|
/** JsPlugin rules whose oxlint names match the ESLint-side names. */
|
|
33264
33265
|
type OxlintKeptJsPluginRuleOptions = { [K in keyof RuleOptions as K extends `${OxlintKeptJsPluginPrefix}/${string}` ? K : never]: RuleOptions[K]; };
|
|
33265
33266
|
/** All rules known to the oxlint factory, keyed by canonical oxlint rule name. */
|
|
@@ -33305,7 +33306,7 @@ type TypedOxlintConfigItem = Omit<OxlintOverride$1, "rules"> & {
|
|
|
33305
33306
|
* Oxlint can only lint JS/TS files, so the JSON, YAML, TOML, Markdown and
|
|
33306
33307
|
* pnpm-related options are omitted, along with ESLint-only options.
|
|
33307
33308
|
*/
|
|
33308
|
-
type OxlintOptionsConfig = Omit<OptionsConfig, "autoRenamePlugins" | "flawless" | "jsonc" | "markdown" | "namedConfigs" | "naming" | "oxlint" | "pnpm" | "
|
|
33309
|
+
type OxlintOptionsConfig = Omit<OptionsConfig, "autoRenamePlugins" | "flawless" | "jsonc" | "markdown" | "namedConfigs" | "naming" | "oxlint" | "pnpm" | "toml" | "yaml">;
|
|
33309
33310
|
/**
|
|
33310
33311
|
* Options accepted by the oxlint factory function.
|
|
33311
33312
|
*
|
package/dist/oxlint.mjs
CHANGED
|
@@ -616,6 +616,9 @@ const jsPluginRuleNames = /* @__PURE__ */ new Set([
|
|
|
616
616
|
"prefer-spread",
|
|
617
617
|
"prefer-template",
|
|
618
618
|
"preserve-caught-error",
|
|
619
|
+
"project-structure/file-composition",
|
|
620
|
+
"project-structure/folder-structure",
|
|
621
|
+
"project-structure/independent-modules",
|
|
619
622
|
"promise/always-return",
|
|
620
623
|
"promise/avoid-new",
|
|
621
624
|
"promise/catch-or-return",
|
|
@@ -4747,6 +4750,7 @@ const oxlintJsPlugins = {
|
|
|
4747
4750
|
"oxfmt": "eslint-plugin-oxfmt",
|
|
4748
4751
|
"oxlint-comments": "oxlint-plugin-oxlint-comments",
|
|
4749
4752
|
"perfectionist": "eslint-plugin-perfectionist",
|
|
4753
|
+
"project-structure": "eslint-plugin-project-structure",
|
|
4750
4754
|
"promise-js": "eslint-plugin-promise",
|
|
4751
4755
|
"react-jsx": "eslint-plugin-react-jsx",
|
|
4752
4756
|
"react-naming-convention": "eslint-plugin-react-naming-convention",
|
|
@@ -5165,6 +5169,23 @@ const GLOB_ROOT = [
|
|
|
5165
5169
|
];
|
|
5166
5170
|
const GLOB_SRC_EXT = "{,c,m}[jt]s{,x}";
|
|
5167
5171
|
const GLOB_SRC = "**/*.{,c,m}[jt]s{,x}";
|
|
5172
|
+
/**
|
|
5173
|
+
* The real TS/JS-family file extensions {@link GLOB_SRC_EXT} encodes (the glob
|
|
5174
|
+
* additionally matches non-existent combinations such as `cjsx`). Kept next to
|
|
5175
|
+
* the glob so the two are maintained together; consumed by the lint CLI to size
|
|
5176
|
+
* its type-aware pass.
|
|
5177
|
+
*/
|
|
5178
|
+
const GLOB_SRC_EXTENSIONS = [
|
|
5179
|
+
"ts",
|
|
5180
|
+
"tsx",
|
|
5181
|
+
"mts",
|
|
5182
|
+
"cts",
|
|
5183
|
+
"js",
|
|
5184
|
+
"jsx",
|
|
5185
|
+
"mjs",
|
|
5186
|
+
"cjs"
|
|
5187
|
+
];
|
|
5188
|
+
[...GLOB_SRC_EXTENSIONS];
|
|
5168
5189
|
const GLOB_JSX = "**/*.{,c,m}jsx";
|
|
5169
5190
|
const GLOB_TSX = "**/*.{,c,m}tsx";
|
|
5170
5191
|
const GLOB_DTS = "**/*.d.{,c,m}ts";
|
|
@@ -8013,6 +8034,94 @@ function oxlintPerfectionist(config) {
|
|
|
8013
8034
|
})];
|
|
8014
8035
|
}
|
|
8015
8036
|
//#endregion
|
|
8037
|
+
//#region src/rules/project-structure.ts
|
|
8038
|
+
/** Stands for the extension of the file a rule matched. */
|
|
8039
|
+
const EXTENSION_TOKEN = "{ext}";
|
|
8040
|
+
const DEFAULT_ENFORCE_EXISTENCE = [`{node-name}.spec.${EXTENSION_TOKEN}`];
|
|
8041
|
+
/** Reusable-rule id for "a folder of any name". */
|
|
8042
|
+
const ANY_FOLDER = "isentinelAnyFolder";
|
|
8043
|
+
/** The files the co-location check runs on, before `ignores`. */
|
|
8044
|
+
const PROJECT_STRUCTURE_FILES = [GLOB_SRC];
|
|
8045
|
+
/** The files the co-location check skips. */
|
|
8046
|
+
const PROJECT_STRUCTURE_IGNORES = [
|
|
8047
|
+
...GLOB_TESTS,
|
|
8048
|
+
GLOB_DTS,
|
|
8049
|
+
...GLOB_BUILD_CONFIGS
|
|
8050
|
+
];
|
|
8051
|
+
/**
|
|
8052
|
+
* Co-location rules shared between the ESLint and oxlint factories.
|
|
8053
|
+
*
|
|
8054
|
+
* `folder-structure` is not type-aware and runs unchanged as an oxlint
|
|
8055
|
+
* jsPlugin, so both engines have to hand it the same options or they disagree
|
|
8056
|
+
* about which files are co-located.
|
|
8057
|
+
*
|
|
8058
|
+
* @param options - The templates to enforce, and the roots they resolve
|
|
8059
|
+
* against.
|
|
8060
|
+
* @returns The rule map.
|
|
8061
|
+
*/
|
|
8062
|
+
function folderStructureRules({ enforceExistence = DEFAULT_ENFORCE_EXISTENCE, projectRoot = process.cwd(), structureRoot } = {}) {
|
|
8063
|
+
const children = buildChildren(typeof enforceExistence === "string" ? [enforceExistence] : enforceExistence);
|
|
8064
|
+
return { "project-structure/folder-structure": ["error", {
|
|
8065
|
+
longPathsInfo: false,
|
|
8066
|
+
projectRoot,
|
|
8067
|
+
rules: { [ANY_FOLDER]: {
|
|
8068
|
+
name: "*",
|
|
8069
|
+
children
|
|
8070
|
+
} },
|
|
8071
|
+
structure: children,
|
|
8072
|
+
...structureRoot === void 0 ? {} : { structureRoot }
|
|
8073
|
+
}] };
|
|
8074
|
+
}
|
|
8075
|
+
/**
|
|
8076
|
+
* The rules matching the files inside one folder, in the order
|
|
8077
|
+
* `folder-structure` tries them - first match wins.
|
|
8078
|
+
*
|
|
8079
|
+
* Every entry is permissive: `structure` is required by the rule and an
|
|
8080
|
+
* unmatched node is an error, so anything less would turn the co-location
|
|
8081
|
+
* check into whole-tree naming enforcement.
|
|
8082
|
+
*
|
|
8083
|
+
* @param templates - The `enforceExistence` templates, before substitution.
|
|
8084
|
+
* @returns The child rules, ending in the recursive folder rule.
|
|
8085
|
+
*/
|
|
8086
|
+
function buildChildren(templates) {
|
|
8087
|
+
const children = templates.map((template) => {
|
|
8088
|
+
return { name: (template.split("/").pop() ?? template).replaceAll(/\{[^{}]*\}/g, "*") };
|
|
8089
|
+
});
|
|
8090
|
+
for (const extension of GLOB_SRC_EXTENSIONS) children.push({
|
|
8091
|
+
name: `*.${extension}`,
|
|
8092
|
+
enforceExistence: templates.map((template) => {
|
|
8093
|
+
return template.replaceAll(EXTENSION_TOKEN, () => extension);
|
|
8094
|
+
})
|
|
8095
|
+
});
|
|
8096
|
+
children.push({ ruleId: ANY_FOLDER });
|
|
8097
|
+
return children;
|
|
8098
|
+
}
|
|
8099
|
+
//#endregion
|
|
8100
|
+
//#region src/oxlint/configs/project-structure.ts
|
|
8101
|
+
/**
|
|
8102
|
+
* Co-location rules for standalone oxlint.
|
|
8103
|
+
*
|
|
8104
|
+
* `folder-structure` runs as a jsPlugin: it visits `Program` once per file and
|
|
8105
|
+
* answers from the filesystem, so oxlint needs no type information to run it.
|
|
8106
|
+
*
|
|
8107
|
+
* @param options - The templates to enforce, and the roots they resolve
|
|
8108
|
+
* against.
|
|
8109
|
+
* @returns The generated config fragments.
|
|
8110
|
+
*/
|
|
8111
|
+
function oxlintProjectStructure({ enforceExistence, files = PROJECT_STRUCTURE_FILES, ignores = PROJECT_STRUCTURE_IGNORES, overrides = {}, projectRoot, structureRoot } = {}) {
|
|
8112
|
+
return createOxlintConfigs({
|
|
8113
|
+
name: "isentinel/project-structure",
|
|
8114
|
+
excludeFiles: ignores,
|
|
8115
|
+
files: files.flat(),
|
|
8116
|
+
overrides,
|
|
8117
|
+
rules: folderStructureRules({
|
|
8118
|
+
enforceExistence,
|
|
8119
|
+
projectRoot,
|
|
8120
|
+
structureRoot
|
|
8121
|
+
})
|
|
8122
|
+
});
|
|
8123
|
+
}
|
|
8124
|
+
//#endregion
|
|
8016
8125
|
//#region src/rules/promise.ts
|
|
8017
8126
|
/**
|
|
8018
8127
|
* Promise rules shared between the ESLint and oxlint factories.
|
|
@@ -9230,7 +9339,7 @@ const NATIVE_ONLY_JS_PLUGINS = /* @__PURE__ */ new Set(["oxlint-comments"]);
|
|
|
9230
9339
|
*/
|
|
9231
9340
|
function isentinel(factoryOptions, ...userConfigs) {
|
|
9232
9341
|
const options = factoryOptions ?? { name: "isentinel" };
|
|
9233
|
-
const { categories, componentExts: componentExtensions = [], e18e: enableE18e = true, env, eslintPlugin: enableEslintPlugin = false, formatters, gitignore: enableGitignore = true, globals, ignores, jsdoc: enableJsdoc = true, jsPlugins: userJsPlugins, jsx: enableJsx = true, options: linterOptions, oxc: enableOxc = true, react: enableReact = false, root: customRootGlobs, rules = {}, spellCheck: enableSpellCheck, test: enableTest = false, warnDroppedOverrides: enableDroppedOverrideWarning = true } = options;
|
|
9342
|
+
const { categories, componentExts: componentExtensions = [], e18e: enableE18e = true, env, eslintPlugin: enableEslintPlugin = false, formatters, gitignore: enableGitignore = true, globals, ignores, jsdoc: enableJsdoc = true, jsPlugins: userJsPlugins, jsx: enableJsx = true, options: linterOptions, oxc: enableOxc = true, projectStructure: enableProjectStructure = false, react: enableReact = false, root: customRootGlobs, rules = {}, spellCheck: enableSpellCheck, test: enableTest = false, warnDroppedOverrides: enableDroppedOverrideWarning = true } = options;
|
|
9234
9343
|
const rootGlobs = mergeGlobs(GLOB_ROOT, customRootGlobs);
|
|
9235
9344
|
const enableRoblox = options.roblox !== false;
|
|
9236
9345
|
const reactOptions = resolveSubOptions(options, "react");
|
|
@@ -9359,6 +9468,7 @@ function isentinel(factoryOptions, ...userConfigs) {
|
|
|
9359
9468
|
isInEditor
|
|
9360
9469
|
}));
|
|
9361
9470
|
if (enableEslintPlugin !== false) configs.push(oxlintEslintPlugin({ ...getOverrides(options, "eslintPlugin") }));
|
|
9471
|
+
if (enableProjectStructure !== false) configs.push(oxlintProjectStructure(resolveSubOptions(options, "projectStructure")));
|
|
9362
9472
|
if (enableOxc) {
|
|
9363
9473
|
configs.push(oxlintOxc({ roblox: enableRoblox }));
|
|
9364
9474
|
if (needsComplementOverlay) configs.push(oxlintOxc({
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@isentinel/eslint-config",
|
|
3
|
-
"version": "6.0.0-beta.
|
|
3
|
+
"version": "6.0.0-beta.45",
|
|
4
4
|
"description": "iSentinel's ESLint config",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"eslint-config",
|
|
@@ -145,7 +145,7 @@
|
|
|
145
145
|
"typescript": "6.0.3",
|
|
146
146
|
"unplugin-unused": "0.5.7",
|
|
147
147
|
"vitest": "4.1.10",
|
|
148
|
-
"@isentinel/eslint-config": "6.0.0-beta.
|
|
148
|
+
"@isentinel/eslint-config": "6.0.0-beta.45"
|
|
149
149
|
},
|
|
150
150
|
"peerDependencies": {
|
|
151
151
|
"@vitest/eslint-plugin": "^1.6.4",
|