pasika 0.10.32 → 0.10.34
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/eslint/pasika/index.d.ts +2 -2
- package/dist/eslint/pasika/index.js +54 -103
- package/package.json +1 -1
|
@@ -142,7 +142,7 @@ declare const vulykRules: {
|
|
|
142
142
|
* One plugin object every preset block references. ESLint only permits a
|
|
143
143
|
* plugin name to be redefined across configs when the value is the same
|
|
144
144
|
* object reference, and several blocks share file scopes (`src/**`, the
|
|
145
|
-
* manifest, `globals.css`), so a per-block plugin object would collide when
|
|
145
|
+
* manifest, `src/app/styles/globals.css`), so a per-block plugin object would collide when
|
|
146
146
|
* the blocks merge.
|
|
147
147
|
*/
|
|
148
148
|
declare const pasikaPlugin: {
|
|
@@ -214,7 +214,7 @@ declare const pasikaPlugin: {
|
|
|
214
214
|
"component-placement": eslint.Rule.RuleModule;
|
|
215
215
|
"component-casing": eslint.Rule.RuleModule;
|
|
216
216
|
"application-structure": eslint.Rule.RuleModule;
|
|
217
|
-
"data-
|
|
217
|
+
"data-component-case": eslint.Rule.RuleModule;
|
|
218
218
|
"jsx-hygiene": eslint.Rule.RuleModule;
|
|
219
219
|
"interactive-component": eslint.Rule.RuleModule;
|
|
220
220
|
"ui-state": eslint.Rule.RuleModule;
|
|
@@ -219,85 +219,40 @@ function findJsxReturningDeclarations(text, filename) {
|
|
|
219
219
|
}
|
|
220
220
|
return results;
|
|
221
221
|
}
|
|
222
|
-
function
|
|
223
|
-
const tagName2 = ts.isJsxElement(element) ? element.openingElement.tagName : element.tagName;
|
|
224
|
-
return ts.isIdentifier(tagName2) ? tagName2.text : void 0;
|
|
225
|
-
}
|
|
226
|
-
function rootFromExpression(expression) {
|
|
227
|
-
let unwrapped = expression;
|
|
228
|
-
while (ts.isParenthesizedExpression(unwrapped)) unwrapped = unwrapped.expression;
|
|
229
|
-
if (ts.isJsxSelfClosingElement(unwrapped)) {
|
|
230
|
-
const tagName2 = jsxTagName(unwrapped);
|
|
231
|
-
if (!tagName2?.startsWith(tagName2[0]?.toLowerCase() ?? "")) return void 0;
|
|
232
|
-
return { tagName: tagName2, attributes: [...unwrapped.attributes.properties] };
|
|
233
|
-
}
|
|
234
|
-
if (ts.isJsxElement(unwrapped)) {
|
|
235
|
-
const tagName2 = jsxTagName(unwrapped);
|
|
236
|
-
if (!tagName2?.startsWith(tagName2[0]?.toLowerCase() ?? "")) return void 0;
|
|
237
|
-
return { tagName: tagName2, attributes: [...unwrapped.openingElement.attributes.properties] };
|
|
238
|
-
}
|
|
239
|
-
return void 0;
|
|
240
|
-
}
|
|
241
|
-
function findSimpleRoot(component, _text, _filename) {
|
|
242
|
-
const declaration = component.declaration;
|
|
243
|
-
let body;
|
|
244
|
-
if (ts.isFunctionDeclaration(declaration)) body = declaration.body;
|
|
245
|
-
else if (declaration.initializer && ts.isArrowFunction(declaration.initializer)) body = declaration.initializer.body;
|
|
246
|
-
else if (declaration.initializer && ts.isFunctionExpression(declaration.initializer)) {
|
|
247
|
-
body = declaration.initializer.body;
|
|
248
|
-
}
|
|
249
|
-
if (!body) return void 0;
|
|
250
|
-
if (!ts.isBlock(body)) return rootFromExpression(body);
|
|
251
|
-
const returns = [];
|
|
252
|
-
const visit = (node) => {
|
|
253
|
-
if (node !== body && (ts.isFunctionLike(node) || ts.isClassLike(node))) return;
|
|
254
|
-
if (ts.isReturnStatement(node)) returns.push(node);
|
|
255
|
-
ts.forEachChild(node, visit);
|
|
256
|
-
};
|
|
257
|
-
visit(body);
|
|
258
|
-
const rendered = returns.filter((statement) => statement.expression?.kind !== ts.SyntaxKind.NullKeyword);
|
|
259
|
-
const roots = rendered.map(
|
|
260
|
-
(statement) => statement.expression && ts.isExpression(statement.expression) ? rootFromExpression(statement.expression) : void 0
|
|
261
|
-
).filter((root) => root !== void 0);
|
|
262
|
-
if (roots.length !== rendered.length || roots.length === 0) return void 0;
|
|
263
|
-
const firstTag = roots[0]?.tagName;
|
|
264
|
-
if (roots.some((root) => root.tagName !== firstTag)) return void 0;
|
|
265
|
-
return roots[0];
|
|
266
|
-
}
|
|
267
|
-
function literalTestIds(attributes) {
|
|
222
|
+
function componentMarkers(attributes) {
|
|
268
223
|
return attributes.flatMap((candidate) => {
|
|
269
|
-
if (!ts.isJsxAttribute(candidate) || !ts.isIdentifier(candidate.name) || candidate.name.text !== "data-
|
|
224
|
+
if (!ts.isJsxAttribute(candidate) || !ts.isIdentifier(candidate.name) || candidate.name.text !== "data-component") {
|
|
270
225
|
return [];
|
|
271
226
|
}
|
|
272
|
-
return candidate.initializer && ts.isStringLiteral(candidate.initializer) ? [candidate.initializer.text] : [];
|
|
227
|
+
return candidate.initializer && ts.isStringLiteral(candidate.initializer) ? [candidate.initializer.text] : ["<dynamic>"];
|
|
273
228
|
});
|
|
274
229
|
}
|
|
275
|
-
function
|
|
230
|
+
function combineComponentMarkerPaths(left, right) {
|
|
276
231
|
return left.flatMap((leftPath) => right.map((rightPath) => [...leftPath, ...rightPath]));
|
|
277
232
|
}
|
|
278
|
-
function
|
|
233
|
+
function renderedComponentMarkerPathsFromExpression(expression) {
|
|
279
234
|
let unwrapped = expression;
|
|
280
235
|
while (ts.isParenthesizedExpression(unwrapped)) unwrapped = unwrapped.expression;
|
|
281
236
|
if (ts.isConditionalExpression(unwrapped)) {
|
|
282
237
|
return [
|
|
283
|
-
...
|
|
284
|
-
...
|
|
238
|
+
...renderedComponentMarkerPathsFromExpression(unwrapped.whenTrue),
|
|
239
|
+
...renderedComponentMarkerPathsFromExpression(unwrapped.whenFalse)
|
|
285
240
|
];
|
|
286
241
|
}
|
|
287
242
|
if (ts.isJsxSelfClosingElement(unwrapped)) {
|
|
288
|
-
return [
|
|
243
|
+
return [componentMarkers(unwrapped.attributes.properties)];
|
|
289
244
|
}
|
|
290
245
|
if (ts.isJsxElement(unwrapped)) {
|
|
291
|
-
let paths = [
|
|
246
|
+
let paths = [componentMarkers(unwrapped.openingElement.attributes.properties)];
|
|
292
247
|
for (const child of unwrapped.children) {
|
|
293
248
|
if (ts.isJsxElement(child) || ts.isJsxSelfClosingElement(child) || ts.isJsxFragment(child)) {
|
|
294
|
-
paths =
|
|
249
|
+
paths = combineComponentMarkerPaths(paths, renderedComponentMarkerPathsFromExpression(child));
|
|
295
250
|
continue;
|
|
296
251
|
}
|
|
297
252
|
if (ts.isJsxExpression(child) && child.expression) {
|
|
298
253
|
const childExpression = child.expression;
|
|
299
254
|
if (ts.isConditionalExpression(childExpression) || ts.isJsxElement(childExpression) || ts.isJsxSelfClosingElement(childExpression) || ts.isJsxFragment(childExpression) || ts.isParenthesizedExpression(childExpression)) {
|
|
300
|
-
paths =
|
|
255
|
+
paths = combineComponentMarkerPaths(paths, renderedComponentMarkerPathsFromExpression(childExpression));
|
|
301
256
|
}
|
|
302
257
|
}
|
|
303
258
|
}
|
|
@@ -307,13 +262,13 @@ function renderedTestIdPathsFromExpression(expression) {
|
|
|
307
262
|
let paths = [[]];
|
|
308
263
|
for (const child of unwrapped.children) {
|
|
309
264
|
if (ts.isJsxElement(child) || ts.isJsxSelfClosingElement(child) || ts.isJsxFragment(child)) {
|
|
310
|
-
paths =
|
|
265
|
+
paths = combineComponentMarkerPaths(paths, renderedComponentMarkerPathsFromExpression(child));
|
|
311
266
|
continue;
|
|
312
267
|
}
|
|
313
268
|
if (ts.isJsxExpression(child) && child.expression) {
|
|
314
269
|
const childExpression = child.expression;
|
|
315
270
|
if (ts.isConditionalExpression(childExpression) || ts.isJsxElement(childExpression) || ts.isJsxSelfClosingElement(childExpression) || ts.isJsxFragment(childExpression) || ts.isParenthesizedExpression(childExpression)) {
|
|
316
|
-
paths =
|
|
271
|
+
paths = combineComponentMarkerPaths(paths, renderedComponentMarkerPathsFromExpression(childExpression));
|
|
317
272
|
}
|
|
318
273
|
}
|
|
319
274
|
}
|
|
@@ -321,7 +276,7 @@ function renderedTestIdPathsFromExpression(expression) {
|
|
|
321
276
|
}
|
|
322
277
|
return [[]];
|
|
323
278
|
}
|
|
324
|
-
function
|
|
279
|
+
function findRenderedComponentMarkerPaths(component) {
|
|
325
280
|
const declaration = component.declaration;
|
|
326
281
|
let body;
|
|
327
282
|
if (ts.isFunctionDeclaration(declaration)) body = declaration.body;
|
|
@@ -330,7 +285,7 @@ function findRenderedTestIdPaths(component) {
|
|
|
330
285
|
body = declaration.initializer.body;
|
|
331
286
|
}
|
|
332
287
|
if (!body) return [];
|
|
333
|
-
if (!ts.isBlock(body)) return
|
|
288
|
+
if (!ts.isBlock(body)) return renderedComponentMarkerPathsFromExpression(body);
|
|
334
289
|
const returns = [];
|
|
335
290
|
const visit = (node) => {
|
|
336
291
|
if (node !== body && (ts.isFunctionLike(node) || ts.isClassLike(node))) return;
|
|
@@ -341,18 +296,9 @@ function findRenderedTestIdPaths(component) {
|
|
|
341
296
|
return returns.flatMap((statement) => {
|
|
342
297
|
const expression = statement.expression;
|
|
343
298
|
if (!expression || expression.kind === ts.SyntaxKind.NullKeyword) return [];
|
|
344
|
-
return
|
|
299
|
+
return renderedComponentMarkerPathsFromExpression(expression);
|
|
345
300
|
});
|
|
346
301
|
}
|
|
347
|
-
function getTestId(root) {
|
|
348
|
-
const attribute = root.attributes.find(
|
|
349
|
-
(candidate) => ts.isJsxAttribute(candidate) && ts.isIdentifier(candidate.name) && candidate.name.text === "data-testid"
|
|
350
|
-
);
|
|
351
|
-
if (!attribute) return {};
|
|
352
|
-
const value = attribute.initializer;
|
|
353
|
-
if (!value || !ts.isStringLiteral(value)) return { attribute };
|
|
354
|
-
return { value: value.text, attribute };
|
|
355
|
-
}
|
|
356
302
|
|
|
357
303
|
// eslint/rules/filename-case.ts
|
|
358
304
|
var NEXT_ROUTING_FILES = /* @__PURE__ */ new Set([
|
|
@@ -2393,7 +2339,7 @@ var namedExportsRule = {
|
|
|
2393
2339
|
}
|
|
2394
2340
|
};
|
|
2395
2341
|
|
|
2396
|
-
// eslint/rules/data-
|
|
2342
|
+
// eslint/rules/data-component-case.ts
|
|
2397
2343
|
import path15 from "path";
|
|
2398
2344
|
var NEXT_ROUTING_FILES2 = /* @__PURE__ */ new Set([
|
|
2399
2345
|
"default",
|
|
@@ -2416,13 +2362,12 @@ var NEXT_ROUTING_FILES2 = /* @__PURE__ */ new Set([
|
|
|
2416
2362
|
"sitemap",
|
|
2417
2363
|
"twitter-image"
|
|
2418
2364
|
]);
|
|
2419
|
-
var
|
|
2420
|
-
var dataTestIdCaseRule = {
|
|
2365
|
+
var dataComponentCaseRule = {
|
|
2421
2366
|
meta: {
|
|
2422
2367
|
schema: [],
|
|
2423
2368
|
type: "problem",
|
|
2424
2369
|
docs: {
|
|
2425
|
-
description: "Enforce data-
|
|
2370
|
+
description: "Enforce stable data-component markers for React component surfaces."
|
|
2426
2371
|
}
|
|
2427
2372
|
},
|
|
2428
2373
|
create(context) {
|
|
@@ -2430,41 +2375,29 @@ var dataTestIdCaseRule = {
|
|
|
2430
2375
|
if (!filename.endsWith(".tsx")) return {};
|
|
2431
2376
|
const base = path15.basename(filename, path15.extname(filename));
|
|
2432
2377
|
if (NEXT_ROUTING_FILES2.has(base)) return {};
|
|
2433
|
-
const
|
|
2434
|
-
const components = parseComponentInfo(text, filename);
|
|
2378
|
+
const components = parseComponentInfo(context.sourceCode.text, filename);
|
|
2435
2379
|
return {
|
|
2436
2380
|
Program(node) {
|
|
2437
2381
|
for (const component of components) {
|
|
2382
|
+
const expected = component.smart ? component.name : toKebabCase(component.name);
|
|
2383
|
+
const renderedPaths = findRenderedComponentMarkerPaths(component);
|
|
2384
|
+
const hasNoMarker = renderedPaths.every((values) => values.length === 0);
|
|
2385
|
+
const hasStableMarker = renderedPaths.length > 0 && renderedPaths.every((values) => values.length === 1 && values[0] === expected);
|
|
2438
2386
|
if (component.smart) {
|
|
2439
|
-
|
|
2440
|
-
const
|
|
2441
|
-
const hasStableAnchor = renderedPaths.length > 0 && renderedPaths.every((values) => values.filter((value2) => value2 === expected2).length === 1);
|
|
2442
|
-
if (hasStableAnchor) continue;
|
|
2443
|
-
const hasSingleWrongCasedAnchor = renderedPaths.length > 0 && renderedPaths.every((values) => values.length === 1 && values[0] !== expected2) && new Set(renderedPaths.map((values) => values[0])).size === 1;
|
|
2444
|
-
if (hasSingleWrongCasedAnchor) {
|
|
2445
|
-
context.report({
|
|
2446
|
-
node,
|
|
2447
|
-
message: `data-testid for smart component "${component.name}" must be PascalCase: expected "${expected2}".`
|
|
2448
|
-
});
|
|
2449
|
-
continue;
|
|
2450
|
-
}
|
|
2387
|
+
if (hasStableMarker) continue;
|
|
2388
|
+
const hasSingleWrongMarker2 = renderedPaths.length > 0 && renderedPaths.every((values) => values.length === 1 && values[0] !== expected) && new Set(renderedPaths.map((values) => values[0])).size === 1;
|
|
2451
2389
|
context.report({
|
|
2452
2390
|
node,
|
|
2453
|
-
message: `Smart component "${component.name}" must expose exactly one stable data-
|
|
2391
|
+
message: hasSingleWrongMarker2 ? `data-component for smart component "${component.name}" must match the component name: expected "${expected}".` : `Smart component "${component.name}" must expose exactly one stable data-component="${expected}" marker in every rendered result. Place it on the existing meaningful DOM surface or forward it to the child component that renders that surface. See docs/next-codebase-guide/rules/smart-vs-dumb-component-rule.md`
|
|
2454
2392
|
});
|
|
2455
2393
|
continue;
|
|
2456
2394
|
}
|
|
2457
|
-
|
|
2458
|
-
|
|
2459
|
-
|
|
2460
|
-
|
|
2461
|
-
|
|
2462
|
-
|
|
2463
|
-
context.report({
|
|
2464
|
-
node,
|
|
2465
|
-
message: `data-testid for dumb component "${component.name}" must be kebab-case: expected "${expected}".`
|
|
2466
|
-
});
|
|
2467
|
-
}
|
|
2395
|
+
if (hasNoMarker || hasStableMarker) continue;
|
|
2396
|
+
const hasSingleWrongMarker = renderedPaths.length > 0 && renderedPaths.every((values) => values.length === 1 && values[0] !== expected) && new Set(renderedPaths.map((values) => values[0])).size === 1;
|
|
2397
|
+
context.report({
|
|
2398
|
+
node,
|
|
2399
|
+
message: hasSingleWrongMarker ? `data-component for dumb component "${component.name}" must be kebab-case: expected "${expected}".` : `Dumb component "${component.name}" may omit data-component, but when present it must expose exactly one stable data-component="${expected}" marker on its meaningful surface.`
|
|
2400
|
+
});
|
|
2468
2401
|
}
|
|
2469
2402
|
}
|
|
2470
2403
|
};
|
|
@@ -7197,12 +7130,14 @@ function buildStylesheetGraph(options) {
|
|
|
7197
7130
|
}
|
|
7198
7131
|
|
|
7199
7132
|
// eslint/rules/tailwind/css-entry-point.ts
|
|
7133
|
+
var APP_STYLES_DIR = path54.join("app", "styles");
|
|
7134
|
+
var GLOBAL_STYLESHEET_PATH = path54.join(APP_STYLES_DIR, "globals.css");
|
|
7200
7135
|
var cssEntryPointRule = {
|
|
7201
7136
|
meta: {
|
|
7202
7137
|
schema: [],
|
|
7203
7138
|
type: "problem",
|
|
7204
7139
|
docs: {
|
|
7205
|
-
description: "Require one global entry point
|
|
7140
|
+
description: "Require the app/styles CSS root, one global entry point, and direct project-CSS imports."
|
|
7206
7141
|
}
|
|
7207
7142
|
},
|
|
7208
7143
|
create(context) {
|
|
@@ -7223,6 +7158,7 @@ var cssEntryPointRule = {
|
|
|
7223
7158
|
"StyleSheet:exit"(node) {
|
|
7224
7159
|
if (globals.length === 0) return;
|
|
7225
7160
|
const current = path54.normalize(path54.resolve(context.filename));
|
|
7161
|
+
const relative = path54.relative(sourceRoot, current);
|
|
7226
7162
|
if (globals.includes(current)) {
|
|
7227
7163
|
if (globals.length > 1) {
|
|
7228
7164
|
context.report({
|
|
@@ -7231,6 +7167,13 @@ var cssEntryPointRule = {
|
|
|
7231
7167
|
});
|
|
7232
7168
|
return;
|
|
7233
7169
|
}
|
|
7170
|
+
if (relative !== GLOBAL_STYLESHEET_PATH) {
|
|
7171
|
+
context.report({
|
|
7172
|
+
node,
|
|
7173
|
+
message: "The global stylesheet entry point must be src/app/styles/globals.css."
|
|
7174
|
+
});
|
|
7175
|
+
return;
|
|
7176
|
+
}
|
|
7234
7177
|
const basename = path54.basename(current);
|
|
7235
7178
|
const importCount = moduleFiles.filter((modulePath) => moduleImports(textOf(modulePath), basename)).length;
|
|
7236
7179
|
if (importCount !== 1) {
|
|
@@ -7241,6 +7184,14 @@ var cssEntryPointRule = {
|
|
|
7241
7184
|
}
|
|
7242
7185
|
return;
|
|
7243
7186
|
}
|
|
7187
|
+
const isInAppStyles = relative === APP_STYLES_DIR || relative.startsWith(`${APP_STYLES_DIR}${path54.sep}`);
|
|
7188
|
+
if (!isInAppStyles) {
|
|
7189
|
+
context.report({
|
|
7190
|
+
node,
|
|
7191
|
+
message: "Project stylesheets must live under src/app/styles/."
|
|
7192
|
+
});
|
|
7193
|
+
return;
|
|
7194
|
+
}
|
|
7244
7195
|
const hasProjectCss = node.children.some((child) => {
|
|
7245
7196
|
if (child.type === "Atrule" && child.name === "import") return false;
|
|
7246
7197
|
if (child.type === "Comment") return false;
|
|
@@ -7968,7 +7919,7 @@ var pasikaNextjsAppRules = {
|
|
|
7968
7919
|
"component-placement": componentPlacementRule,
|
|
7969
7920
|
"component-casing": componentCasingRule,
|
|
7970
7921
|
"application-structure": applicationStructureRule,
|
|
7971
|
-
"data-
|
|
7922
|
+
"data-component-case": dataComponentCaseRule,
|
|
7972
7923
|
"jsx-hygiene": jsxHygieneRule,
|
|
7973
7924
|
"interactive-component": interactiveComponentRule,
|
|
7974
7925
|
"ui-state": uiStateRule,
|
|
@@ -8049,7 +8000,7 @@ var pasikaNextjsAppConfig = {
|
|
|
8049
8000
|
rules: Object.fromEntries(pasikaNextjsAppRuleIds.map((id) => [id, "error"]))
|
|
8050
8001
|
};
|
|
8051
8002
|
var tailwindStructureRules = {
|
|
8052
|
-
files: ["src
|
|
8003
|
+
files: ["src/app/styles/globals.css"],
|
|
8053
8004
|
plugins: {
|
|
8054
8005
|
css,
|
|
8055
8006
|
pasika: pasikaPlugin
|