@slip-stream-kit/eslint-plugin 0.10.0 → 0.11.10
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/dist/index.js +338 -10
- package/dist/index.js.map +4 -4
- package/dist/rules/package-structure/index.d.ts +1 -0
- package/dist/rules/package-structure/package-structure.d.ts +27 -0
- package/dist/utils/package-root.d.ts +16 -0
- package/dist/utils/package-type-reader.d.ts +24 -0
- package/package.json +2 -1
- package/readme.md +141 -0
package/dist/index.js
CHANGED
|
@@ -1001,6 +1001,320 @@ var maxJsxReturnSize = {
|
|
|
1001
1001
|
}
|
|
1002
1002
|
};
|
|
1003
1003
|
|
|
1004
|
+
// src/utils/package-root.ts
|
|
1005
|
+
import fs from "node:fs";
|
|
1006
|
+
import path from "node:path";
|
|
1007
|
+
var CONFIG_FILENAME = "infra-kit.config.ts";
|
|
1008
|
+
var PACKAGE_MARKERS = [CONFIG_FILENAME, "package.json"];
|
|
1009
|
+
var REPO_MARKERS = ["pnpm-workspace.yaml"];
|
|
1010
|
+
var hasAnyMarker = (dir, markers) => {
|
|
1011
|
+
return markers.some((marker) => {
|
|
1012
|
+
return fs.existsSync(path.posix.join(dir, marker));
|
|
1013
|
+
});
|
|
1014
|
+
};
|
|
1015
|
+
var rootCache = /* @__PURE__ */ new Map();
|
|
1016
|
+
var findUp = (startDir, markers) => {
|
|
1017
|
+
let current = startDir;
|
|
1018
|
+
while (!hasAnyMarker(current, markers)) {
|
|
1019
|
+
const parent = path.posix.dirname(current);
|
|
1020
|
+
if (parent === current) {
|
|
1021
|
+
return null;
|
|
1022
|
+
}
|
|
1023
|
+
current = parent;
|
|
1024
|
+
}
|
|
1025
|
+
return current;
|
|
1026
|
+
};
|
|
1027
|
+
var findPackageRoot = (filename) => {
|
|
1028
|
+
if (!path.posix.isAbsolute(filename)) {
|
|
1029
|
+
return null;
|
|
1030
|
+
}
|
|
1031
|
+
const dir = path.posix.dirname(filename);
|
|
1032
|
+
const cached = rootCache.get(dir);
|
|
1033
|
+
if (cached !== void 0) {
|
|
1034
|
+
return cached;
|
|
1035
|
+
}
|
|
1036
|
+
const root = findUp(dir, PACKAGE_MARKERS);
|
|
1037
|
+
rootCache.set(dir, root);
|
|
1038
|
+
return root;
|
|
1039
|
+
};
|
|
1040
|
+
var dirsUnderSrc = (packageRoot, filename) => {
|
|
1041
|
+
const srcPrefix = `${packageRoot}/src/`;
|
|
1042
|
+
if (!filename.startsWith(srcPrefix)) {
|
|
1043
|
+
return null;
|
|
1044
|
+
}
|
|
1045
|
+
return filename.slice(srcPrefix.length).split("/").slice(0, -1);
|
|
1046
|
+
};
|
|
1047
|
+
var findRepoRoot = (packageRoot) => {
|
|
1048
|
+
return findUp(packageRoot, REPO_MARKERS);
|
|
1049
|
+
};
|
|
1050
|
+
|
|
1051
|
+
// ../config/dist/package-type.js
|
|
1052
|
+
import path2 from "node:path";
|
|
1053
|
+
var PACKAGE_TYPES = ["frontend", "backend", "lib", "e2e", "mobile"];
|
|
1054
|
+
var APP_DIRECTORY_TYPES = {
|
|
1055
|
+
ui: "frontend",
|
|
1056
|
+
api: "backend",
|
|
1057
|
+
tests: "e2e"
|
|
1058
|
+
};
|
|
1059
|
+
var MOBILE_BASENAME = "mobile-app";
|
|
1060
|
+
var DEPENDENCY_SIGNALS = [
|
|
1061
|
+
{ type: "e2e", packages: ["@playwright/test"] },
|
|
1062
|
+
{ type: "mobile", packages: ["@capacitor/core", "@capacitor/cli"] },
|
|
1063
|
+
// `aws-lambda` / `@types/aws-lambda` are deliberately NOT signals: every consumer backend already
|
|
1064
|
+
// lives at `apps/<app>/api` (row 2), while the only packages those two deps would catch are
|
|
1065
|
+
// libraries (`packages/lib-core` in both consumers, the infra-kit CLI itself) — a lambda types
|
|
1066
|
+
// dependency alone never means backend, exactly as `react` alone never means frontend.
|
|
1067
|
+
{ type: "backend", packages: ["serverless"] }
|
|
1068
|
+
];
|
|
1069
|
+
var fromDirectory = (packageDir, repoRoot) => {
|
|
1070
|
+
const segments = path2.relative(repoRoot, packageDir).split(path2.sep).filter((segment) => {
|
|
1071
|
+
return segment !== "" && segment !== ".";
|
|
1072
|
+
});
|
|
1073
|
+
const [first, , third] = segments;
|
|
1074
|
+
if (segments.length === 3 && first === "apps" && third !== void 0) {
|
|
1075
|
+
const appType = APP_DIRECTORY_TYPES[third];
|
|
1076
|
+
if (appType) return appType;
|
|
1077
|
+
}
|
|
1078
|
+
return segments.at(-1) === MOBILE_BASENAME ? "mobile" : null;
|
|
1079
|
+
};
|
|
1080
|
+
var fromDependencies = (pkgJson) => {
|
|
1081
|
+
const deps = /* @__PURE__ */ new Set([
|
|
1082
|
+
...Object.keys(pkgJson.dependencies ?? {}),
|
|
1083
|
+
...Object.keys(pkgJson.devDependencies ?? {}),
|
|
1084
|
+
...Object.keys(pkgJson.peerDependencies ?? {})
|
|
1085
|
+
]);
|
|
1086
|
+
for (const signal of DEPENDENCY_SIGNALS) {
|
|
1087
|
+
if (signal.packages.some((name) => {
|
|
1088
|
+
return deps.has(name);
|
|
1089
|
+
}))
|
|
1090
|
+
return signal.type;
|
|
1091
|
+
}
|
|
1092
|
+
const isViteReactApp = deps.has("vite") && deps.has("react") && Boolean(pkgJson.scripts?.dev);
|
|
1093
|
+
return isViteReactApp ? "frontend" : null;
|
|
1094
|
+
};
|
|
1095
|
+
var detectPackageType = ({
|
|
1096
|
+
packageDir,
|
|
1097
|
+
repoRoot,
|
|
1098
|
+
pkgJson,
|
|
1099
|
+
declaredType
|
|
1100
|
+
}) => {
|
|
1101
|
+
return declaredType ?? fromDirectory(packageDir, repoRoot) ?? fromDependencies(pkgJson) ?? "lib";
|
|
1102
|
+
};
|
|
1103
|
+
|
|
1104
|
+
// src/utils/package-type-reader.ts
|
|
1105
|
+
import fs2 from "node:fs";
|
|
1106
|
+
import path3 from "node:path";
|
|
1107
|
+
var BLOCK_COMMENT = /\/\*[\s\S]*?\*\//g;
|
|
1108
|
+
var FULL_LINE_COMMENT = /^[ \t]*\/\/.*$/gm;
|
|
1109
|
+
var DECLARED_TYPE = new RegExp(`(?:^|[\\s{,])type:\\s*['"](${PACKAGE_TYPES.join("|")})['"]`, "m");
|
|
1110
|
+
var readDeclaredType = (text) => {
|
|
1111
|
+
const stripped = text.replace(BLOCK_COMMENT, "").replace(FULL_LINE_COMMENT, "");
|
|
1112
|
+
const literal = DECLARED_TYPE.exec(stripped)?.[1];
|
|
1113
|
+
return PACKAGE_TYPES.find((candidate) => {
|
|
1114
|
+
return candidate === literal;
|
|
1115
|
+
});
|
|
1116
|
+
};
|
|
1117
|
+
var typeCache = /* @__PURE__ */ new Map();
|
|
1118
|
+
var configMtimeMs = (configPath) => {
|
|
1119
|
+
try {
|
|
1120
|
+
return fs2.statSync(configPath).mtimeMs;
|
|
1121
|
+
} catch {
|
|
1122
|
+
return -1;
|
|
1123
|
+
}
|
|
1124
|
+
};
|
|
1125
|
+
var readManifest = (manifestPath) => {
|
|
1126
|
+
try {
|
|
1127
|
+
return JSON.parse(fs2.readFileSync(manifestPath, "utf8"));
|
|
1128
|
+
} catch {
|
|
1129
|
+
return {};
|
|
1130
|
+
}
|
|
1131
|
+
};
|
|
1132
|
+
var resolvePackageType = (packageRoot) => {
|
|
1133
|
+
const configPath = path3.posix.join(packageRoot, CONFIG_FILENAME);
|
|
1134
|
+
const mtimeMs = configMtimeMs(configPath);
|
|
1135
|
+
const hit = typeCache.get(packageRoot);
|
|
1136
|
+
if (hit !== void 0 && hit.mtimeMs === mtimeMs) {
|
|
1137
|
+
return hit.type;
|
|
1138
|
+
}
|
|
1139
|
+
const declaredType = mtimeMs >= 0 ? readDeclaredType(fs2.readFileSync(configPath, "utf8")) : void 0;
|
|
1140
|
+
const type = detectPackageType({
|
|
1141
|
+
packageDir: packageRoot,
|
|
1142
|
+
repoRoot: findRepoRoot(packageRoot) ?? packageRoot,
|
|
1143
|
+
pkgJson: readManifest(path3.posix.join(packageRoot, "package.json")),
|
|
1144
|
+
declaredType
|
|
1145
|
+
});
|
|
1146
|
+
typeCache.set(packageRoot, { mtimeMs, type });
|
|
1147
|
+
return type;
|
|
1148
|
+
};
|
|
1149
|
+
|
|
1150
|
+
// src/rules/package-structure/package-structure.ts
|
|
1151
|
+
var DEFAULT_ENTRIES = {
|
|
1152
|
+
frontend: {
|
|
1153
|
+
layers: ["app", "features", "lib", "components", "pages", "routes"],
|
|
1154
|
+
segments: {
|
|
1155
|
+
"features/*": ["containers", "components", "services", "__stories__", "__tests__"]
|
|
1156
|
+
},
|
|
1157
|
+
skill: "/infra-kit:fe-architect"
|
|
1158
|
+
},
|
|
1159
|
+
backend: {
|
|
1160
|
+
layers: ["controllers", "services", "lib", "config"],
|
|
1161
|
+
segments: {
|
|
1162
|
+
"services/*": ["__tests__"]
|
|
1163
|
+
},
|
|
1164
|
+
// The skill does not exist yet; the user adds `plugins/infra-kit/skills/be-architect/SKILL.md`
|
|
1165
|
+
// and accepted the dangling pointer until then.
|
|
1166
|
+
skill: "/infra-kit:be-architect"
|
|
1167
|
+
},
|
|
1168
|
+
e2e: {
|
|
1169
|
+
layers: ["tests", "pages", "config", "lib", "components", "mocks", "fixtures"],
|
|
1170
|
+
segments: {
|
|
1171
|
+
"tests/*": ["fixtures", "data", "mocks"]
|
|
1172
|
+
},
|
|
1173
|
+
skill: "/infra-kit:e2e-architect"
|
|
1174
|
+
}
|
|
1175
|
+
};
|
|
1176
|
+
var resolveEntry = (type, options) => {
|
|
1177
|
+
return options[type] ?? DEFAULT_ENTRIES[type];
|
|
1178
|
+
};
|
|
1179
|
+
var UNIT_KEY_SUFFIX = "/*";
|
|
1180
|
+
var assertSegmentKeysNameLayers = (type, entry) => {
|
|
1181
|
+
for (const key of Object.keys(entry.segments ?? {})) {
|
|
1182
|
+
const layer = key.endsWith(UNIT_KEY_SUFFIX) ? key.slice(0, -UNIT_KEY_SUFFIX.length) : key;
|
|
1183
|
+
if (!entry.layers.includes(layer)) {
|
|
1184
|
+
throw new Error(
|
|
1185
|
+
`@wl/package-structure: \`${type}.segments\` key "${key}" names a layer that is not in \`${type}.layers\` (${entry.layers.join(", ")}).`
|
|
1186
|
+
);
|
|
1187
|
+
}
|
|
1188
|
+
}
|
|
1189
|
+
};
|
|
1190
|
+
var LAYER_ENTRY_SCHEMA = {
|
|
1191
|
+
type: "object",
|
|
1192
|
+
properties: {
|
|
1193
|
+
layers: {
|
|
1194
|
+
type: "array",
|
|
1195
|
+
items: { type: "string", minLength: 1 },
|
|
1196
|
+
uniqueItems: true,
|
|
1197
|
+
description: "First-level directories allowed under `src/`."
|
|
1198
|
+
},
|
|
1199
|
+
segments: {
|
|
1200
|
+
type: "object",
|
|
1201
|
+
propertyNames: { pattern: "^[^/]+(/\\*)?$" },
|
|
1202
|
+
additionalProperties: {
|
|
1203
|
+
type: "array",
|
|
1204
|
+
items: { type: "string", minLength: 1 },
|
|
1205
|
+
uniqueItems: true
|
|
1206
|
+
},
|
|
1207
|
+
description: "Allowed directories one level down, keyed by `<layer>` (directly inside it) or `<layer>/*` (inside each of its folders)."
|
|
1208
|
+
},
|
|
1209
|
+
skill: {
|
|
1210
|
+
type: "string",
|
|
1211
|
+
description: 'Skill or doc that owns this layout, appended to the message; `""` drops the sentence.'
|
|
1212
|
+
}
|
|
1213
|
+
},
|
|
1214
|
+
required: ["layers"],
|
|
1215
|
+
additionalProperties: false
|
|
1216
|
+
};
|
|
1217
|
+
var findSegmentViolation = (dirs, entry) => {
|
|
1218
|
+
const [layer, second, third] = dirs;
|
|
1219
|
+
const segments = entry.segments ?? {};
|
|
1220
|
+
const perUnit = segments[`${layer}${UNIT_KEY_SUFFIX}`];
|
|
1221
|
+
if (perUnit !== void 0) {
|
|
1222
|
+
return second !== void 0 && third !== void 0 && !perUnit.includes(third) ? { messageId: "unknownSegment", name: third, scope: `${layer}/${second}`, allowed: perUnit } : null;
|
|
1223
|
+
}
|
|
1224
|
+
const direct = segments[layer];
|
|
1225
|
+
if (direct !== void 0 && second !== void 0 && !direct.includes(second)) {
|
|
1226
|
+
return { messageId: "unknownSegment", name: second, scope: layer, allowed: direct };
|
|
1227
|
+
}
|
|
1228
|
+
return null;
|
|
1229
|
+
};
|
|
1230
|
+
var findViolation = (dirs, entry) => {
|
|
1231
|
+
const layer = dirs[0];
|
|
1232
|
+
if (!entry.layers.includes(layer)) {
|
|
1233
|
+
return { messageId: "unknownLayer", name: layer, scope: "src", allowed: entry.layers };
|
|
1234
|
+
}
|
|
1235
|
+
return findSegmentViolation(dirs, entry);
|
|
1236
|
+
};
|
|
1237
|
+
var inspect = (filename, options) => {
|
|
1238
|
+
const packageRoot = findPackageRoot(filename);
|
|
1239
|
+
if (packageRoot === null) {
|
|
1240
|
+
return null;
|
|
1241
|
+
}
|
|
1242
|
+
const dirs = dirsUnderSrc(packageRoot, filename);
|
|
1243
|
+
if (dirs === null || dirs.length === 0) {
|
|
1244
|
+
return null;
|
|
1245
|
+
}
|
|
1246
|
+
const type = resolvePackageType(packageRoot);
|
|
1247
|
+
const entry = resolveEntry(type, options);
|
|
1248
|
+
if (entry === void 0) {
|
|
1249
|
+
return null;
|
|
1250
|
+
}
|
|
1251
|
+
assertSegmentKeysNameLayers(type, entry);
|
|
1252
|
+
const violation = findViolation(dirs, entry);
|
|
1253
|
+
return violation === null ? null : { violation, type, entry };
|
|
1254
|
+
};
|
|
1255
|
+
var packageStructure = {
|
|
1256
|
+
meta: {
|
|
1257
|
+
type: "problem",
|
|
1258
|
+
docs: {
|
|
1259
|
+
description: "Allow only the `src/` layers, and the segments inside them, that belong to the package type.",
|
|
1260
|
+
recommended: true,
|
|
1261
|
+
url: "https://github.com/ArthurSaenz/infra-kit/tree/main/apps/infra-kit/eslint-plugin#package-structure"
|
|
1262
|
+
},
|
|
1263
|
+
schema: [
|
|
1264
|
+
{
|
|
1265
|
+
type: "object",
|
|
1266
|
+
properties: {
|
|
1267
|
+
frontend: LAYER_ENTRY_SCHEMA,
|
|
1268
|
+
backend: LAYER_ENTRY_SCHEMA,
|
|
1269
|
+
lib: LAYER_ENTRY_SCHEMA,
|
|
1270
|
+
e2e: LAYER_ENTRY_SCHEMA,
|
|
1271
|
+
mobile: LAYER_ENTRY_SCHEMA,
|
|
1272
|
+
ignore: {
|
|
1273
|
+
type: "array",
|
|
1274
|
+
items: { type: "string" },
|
|
1275
|
+
description: "Optional globs; the rule is skipped for matching files."
|
|
1276
|
+
}
|
|
1277
|
+
},
|
|
1278
|
+
additionalProperties: false
|
|
1279
|
+
}
|
|
1280
|
+
],
|
|
1281
|
+
messages: {
|
|
1282
|
+
unknownLayer: "`{{name}}` is not an allowed `src/` layer for package type `{{type}}` (allowed: {{allowed}}).{{seeSkill}}",
|
|
1283
|
+
unknownSegment: "`{{name}}` is not an allowed segment of `{{scope}}` for package type `{{type}}` (allowed: {{allowed}}).{{seeSkill}}"
|
|
1284
|
+
}
|
|
1285
|
+
},
|
|
1286
|
+
create(context) {
|
|
1287
|
+
const options = context.options[0] ?? {};
|
|
1288
|
+
const ignore = options.ignore ?? [];
|
|
1289
|
+
if (ignore.length > 0 && matchesAnyGlob(context.filename, ignore)) {
|
|
1290
|
+
return {};
|
|
1291
|
+
}
|
|
1292
|
+
const found = inspect(context.filename, options);
|
|
1293
|
+
if (found === null) {
|
|
1294
|
+
return {};
|
|
1295
|
+
}
|
|
1296
|
+
const { violation, type, entry } = found;
|
|
1297
|
+
return {
|
|
1298
|
+
// Reported on Program so the IDE marks the file from line 1: every file under the offending
|
|
1299
|
+
// directory carries the same message, which is how a "red folder" surfaces in the explorer.
|
|
1300
|
+
Program(program) {
|
|
1301
|
+
context.report({
|
|
1302
|
+
node: program,
|
|
1303
|
+
messageId: violation.messageId,
|
|
1304
|
+
data: {
|
|
1305
|
+
name: violation.name,
|
|
1306
|
+
scope: violation.scope,
|
|
1307
|
+
type,
|
|
1308
|
+
allowed: violation.allowed.join(", "),
|
|
1309
|
+
// Message templates have no conditionals, so the optional sentence is assembled here.
|
|
1310
|
+
seeSkill: entry.skill ? ` See ${entry.skill} for the ${type} layout.` : ""
|
|
1311
|
+
}
|
|
1312
|
+
});
|
|
1313
|
+
}
|
|
1314
|
+
};
|
|
1315
|
+
}
|
|
1316
|
+
};
|
|
1317
|
+
|
|
1004
1318
|
// src/rules/props-destructuring-blank-line/props-destructuring-blank-line.ts
|
|
1005
1319
|
var isPropsDestructuring = (statement) => {
|
|
1006
1320
|
if (statement.type !== "VariableDeclaration") {
|
|
@@ -1315,10 +1629,10 @@ var propsTypeReference = {
|
|
|
1315
1629
|
|
|
1316
1630
|
// src/rules/require-component-stories/require-component-stories.ts
|
|
1317
1631
|
import { existsSync } from "node:fs";
|
|
1318
|
-
import
|
|
1632
|
+
import path5 from "node:path";
|
|
1319
1633
|
|
|
1320
1634
|
// src/utils/story-path.ts
|
|
1321
|
-
import
|
|
1635
|
+
import path4 from "node:path";
|
|
1322
1636
|
var DEFAULT_STORY_PATH_OPTIONS = {
|
|
1323
1637
|
storiesDir: "__stories__",
|
|
1324
1638
|
storySuffix: ".stories",
|
|
@@ -1337,10 +1651,10 @@ var parse = (filePath) => {
|
|
|
1337
1651
|
const normalized = toPosix(filePath);
|
|
1338
1652
|
const segments = normalized.split("/");
|
|
1339
1653
|
const basename = segments[segments.length - 1] ?? "";
|
|
1340
|
-
const ext =
|
|
1654
|
+
const ext = path4.posix.extname(basename);
|
|
1341
1655
|
const base = ext ? basename.slice(0, -ext.length) : basename;
|
|
1342
1656
|
return {
|
|
1343
|
-
dir:
|
|
1657
|
+
dir: path4.posix.dirname(normalized),
|
|
1344
1658
|
base,
|
|
1345
1659
|
ext,
|
|
1346
1660
|
parent: segments[segments.length - 2] ?? "",
|
|
@@ -1382,10 +1696,10 @@ var deriveExpectedStoryPaths = (filePath, opts) => {
|
|
|
1382
1696
|
return [];
|
|
1383
1697
|
}
|
|
1384
1698
|
const parsed = parse(filePath);
|
|
1385
|
-
const storyBaseDir = classification.mode === "feature-root" ?
|
|
1386
|
-
const storyDir =
|
|
1699
|
+
const storyBaseDir = classification.mode === "feature-root" ? path4.posix.dirname(parsed.dir) : parsed.dir;
|
|
1700
|
+
const storyDir = path4.posix.join(storyBaseDir, options.storiesDir);
|
|
1387
1701
|
return options.storyExtensions.map((extension) => {
|
|
1388
|
-
return
|
|
1702
|
+
return path4.posix.join(storyDir, `${parsed.base}${options.storySuffix}${extension}`);
|
|
1389
1703
|
});
|
|
1390
1704
|
};
|
|
1391
1705
|
|
|
@@ -1505,7 +1819,7 @@ var requireComponentStories = {
|
|
|
1505
1819
|
node: program,
|
|
1506
1820
|
messageId: "missingStory",
|
|
1507
1821
|
data: {
|
|
1508
|
-
component:
|
|
1822
|
+
component: path5.posix.basename(context.filename.split("\\").join("/")),
|
|
1509
1823
|
expected: candidates[0] ?? ""
|
|
1510
1824
|
}
|
|
1511
1825
|
});
|
|
@@ -1803,7 +2117,8 @@ var rules = {
|
|
|
1803
2117
|
"max-jsdoc-lines": maxJsdocLines,
|
|
1804
2118
|
"max-jsdoc-summary-lines": maxJsdocSummaryLines,
|
|
1805
2119
|
"require-component-stories": requireComponentStories,
|
|
1806
|
-
"require-jsdoc-example": requireJsdocExample
|
|
2120
|
+
"require-jsdoc-example": requireJsdocExample,
|
|
2121
|
+
"package-structure": packageStructure
|
|
1807
2122
|
};
|
|
1808
2123
|
|
|
1809
2124
|
// src/index.ts
|
|
@@ -1811,7 +2126,7 @@ var PLUGIN_NAME = "@wl";
|
|
|
1811
2126
|
var plugin = {
|
|
1812
2127
|
meta: {
|
|
1813
2128
|
name: "@wl/eslint-plugin",
|
|
1814
|
-
version: "0.10
|
|
2129
|
+
version: "0.11.10"
|
|
1815
2130
|
},
|
|
1816
2131
|
rules,
|
|
1817
2132
|
configs: {}
|
|
@@ -1887,6 +2202,19 @@ plugin.configs.recommended = [
|
|
|
1887
2202
|
rules: {
|
|
1888
2203
|
[`${PLUGIN_NAME}/require-jsdoc-example`]: "warn"
|
|
1889
2204
|
}
|
|
2205
|
+
},
|
|
2206
|
+
// `package-structure` judges a file by its directory, not its contents, so it must reach every
|
|
2207
|
+
// source extension — the `**/*.tsx` block above would never see a `.ts` under `src/core/`. Its
|
|
2208
|
+
// own block (like `require-jsdoc-example`) with `plugins` re-declared keeps it self-contained.
|
|
2209
|
+
// `error` from day one: the per-type entries are overridable per package via `config({ rules })`.
|
|
2210
|
+
{
|
|
2211
|
+
files: ["**/*.{ts,tsx,js,jsx}"],
|
|
2212
|
+
plugins: {
|
|
2213
|
+
[PLUGIN_NAME]: plugin
|
|
2214
|
+
},
|
|
2215
|
+
rules: {
|
|
2216
|
+
[`${PLUGIN_NAME}/package-structure`]: "error"
|
|
2217
|
+
}
|
|
1890
2218
|
}
|
|
1891
2219
|
];
|
|
1892
2220
|
var meta = plugin.meta;
|