pasika 0.10.23 → 0.10.24
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.js +103 -18
- package/package.json +1 -1
|
@@ -264,6 +264,86 @@ function findSimpleRoot(component, _text, _filename) {
|
|
|
264
264
|
if (roots.some((root) => root.tagName !== firstTag)) return void 0;
|
|
265
265
|
return roots[0];
|
|
266
266
|
}
|
|
267
|
+
function literalTestIds(attributes) {
|
|
268
|
+
return attributes.flatMap((candidate) => {
|
|
269
|
+
if (!ts.isJsxAttribute(candidate) || !ts.isIdentifier(candidate.name) || candidate.name.text !== "data-testid") {
|
|
270
|
+
return [];
|
|
271
|
+
}
|
|
272
|
+
return candidate.initializer && ts.isStringLiteral(candidate.initializer) ? [candidate.initializer.text] : [];
|
|
273
|
+
});
|
|
274
|
+
}
|
|
275
|
+
function combineTestIdPaths(left, right) {
|
|
276
|
+
return left.flatMap((leftPath) => right.map((rightPath) => [...leftPath, ...rightPath]));
|
|
277
|
+
}
|
|
278
|
+
function renderedTestIdPathsFromExpression(expression) {
|
|
279
|
+
let unwrapped = expression;
|
|
280
|
+
while (ts.isParenthesizedExpression(unwrapped)) unwrapped = unwrapped.expression;
|
|
281
|
+
if (ts.isConditionalExpression(unwrapped)) {
|
|
282
|
+
return [
|
|
283
|
+
...renderedTestIdPathsFromExpression(unwrapped.whenTrue),
|
|
284
|
+
...renderedTestIdPathsFromExpression(unwrapped.whenFalse)
|
|
285
|
+
];
|
|
286
|
+
}
|
|
287
|
+
if (ts.isJsxSelfClosingElement(unwrapped)) {
|
|
288
|
+
return [literalTestIds(unwrapped.attributes.properties)];
|
|
289
|
+
}
|
|
290
|
+
if (ts.isJsxElement(unwrapped)) {
|
|
291
|
+
let paths = [literalTestIds(unwrapped.openingElement.attributes.properties)];
|
|
292
|
+
for (const child of unwrapped.children) {
|
|
293
|
+
if (ts.isJsxElement(child) || ts.isJsxSelfClosingElement(child) || ts.isJsxFragment(child)) {
|
|
294
|
+
paths = combineTestIdPaths(paths, renderedTestIdPathsFromExpression(child));
|
|
295
|
+
continue;
|
|
296
|
+
}
|
|
297
|
+
if (ts.isJsxExpression(child) && child.expression) {
|
|
298
|
+
const childExpression = child.expression;
|
|
299
|
+
if (ts.isConditionalExpression(childExpression) || ts.isJsxElement(childExpression) || ts.isJsxSelfClosingElement(childExpression) || ts.isJsxFragment(childExpression) || ts.isParenthesizedExpression(childExpression)) {
|
|
300
|
+
paths = combineTestIdPaths(paths, renderedTestIdPathsFromExpression(childExpression));
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
return paths;
|
|
305
|
+
}
|
|
306
|
+
if (ts.isJsxFragment(unwrapped)) {
|
|
307
|
+
let paths = [[]];
|
|
308
|
+
for (const child of unwrapped.children) {
|
|
309
|
+
if (ts.isJsxElement(child) || ts.isJsxSelfClosingElement(child) || ts.isJsxFragment(child)) {
|
|
310
|
+
paths = combineTestIdPaths(paths, renderedTestIdPathsFromExpression(child));
|
|
311
|
+
continue;
|
|
312
|
+
}
|
|
313
|
+
if (ts.isJsxExpression(child) && child.expression) {
|
|
314
|
+
const childExpression = child.expression;
|
|
315
|
+
if (ts.isConditionalExpression(childExpression) || ts.isJsxElement(childExpression) || ts.isJsxSelfClosingElement(childExpression) || ts.isJsxFragment(childExpression) || ts.isParenthesizedExpression(childExpression)) {
|
|
316
|
+
paths = combineTestIdPaths(paths, renderedTestIdPathsFromExpression(childExpression));
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
return paths;
|
|
321
|
+
}
|
|
322
|
+
return [[]];
|
|
323
|
+
}
|
|
324
|
+
function findRenderedTestIdPaths(component) {
|
|
325
|
+
const declaration = component.declaration;
|
|
326
|
+
let body;
|
|
327
|
+
if (ts.isFunctionDeclaration(declaration)) body = declaration.body;
|
|
328
|
+
else if (declaration.initializer && ts.isArrowFunction(declaration.initializer)) body = declaration.initializer.body;
|
|
329
|
+
else if (declaration.initializer && ts.isFunctionExpression(declaration.initializer)) {
|
|
330
|
+
body = declaration.initializer.body;
|
|
331
|
+
}
|
|
332
|
+
if (!body) return [];
|
|
333
|
+
if (!ts.isBlock(body)) return renderedTestIdPathsFromExpression(body);
|
|
334
|
+
const returns = [];
|
|
335
|
+
const visit = (node) => {
|
|
336
|
+
if (node !== body && (ts.isFunctionLike(node) || ts.isClassLike(node))) return;
|
|
337
|
+
if (ts.isReturnStatement(node)) returns.push(node);
|
|
338
|
+
ts.forEachChild(node, visit);
|
|
339
|
+
};
|
|
340
|
+
visit(body);
|
|
341
|
+
return returns.flatMap((statement) => {
|
|
342
|
+
const expression = statement.expression;
|
|
343
|
+
if (!expression || expression.kind === ts.SyntaxKind.NullKeyword) return [];
|
|
344
|
+
return renderedTestIdPathsFromExpression(expression);
|
|
345
|
+
});
|
|
346
|
+
}
|
|
267
347
|
function getTestId(root) {
|
|
268
348
|
const attribute = root.attributes.find(
|
|
269
349
|
(candidate) => ts.isJsxAttribute(candidate) && ts.isIdentifier(candidate.name) && candidate.name.text === "data-testid"
|
|
@@ -2193,7 +2273,6 @@ var NEXT_ROUTING_FILES2 = /* @__PURE__ */ new Set([
|
|
|
2193
2273
|
"sitemap",
|
|
2194
2274
|
"twitter-image"
|
|
2195
2275
|
]);
|
|
2196
|
-
var isPascalCase5 = (value) => /^[A-Z][A-Za-z0-9]*$/.test(value);
|
|
2197
2276
|
var isKebabCase3 = (value) => /^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/.test(value);
|
|
2198
2277
|
var dataTestIdCaseRule = {
|
|
2199
2278
|
meta: {
|
|
@@ -2213,28 +2292,34 @@ var dataTestIdCaseRule = {
|
|
|
2213
2292
|
return {
|
|
2214
2293
|
Program(node) {
|
|
2215
2294
|
for (const component of components) {
|
|
2216
|
-
|
|
2217
|
-
|
|
2218
|
-
const
|
|
2219
|
-
const
|
|
2220
|
-
if (
|
|
2295
|
+
if (component.smart) {
|
|
2296
|
+
const expected2 = component.name;
|
|
2297
|
+
const renderedPaths = findRenderedTestIdPaths(component);
|
|
2298
|
+
const hasStableAnchor = renderedPaths.length > 0 && renderedPaths.every((values) => values.filter((value2) => value2 === expected2).length === 1);
|
|
2299
|
+
if (hasStableAnchor) continue;
|
|
2300
|
+
const hasSingleWrongCasedAnchor = renderedPaths.length > 0 && renderedPaths.every((values) => values.length === 1 && values[0] !== expected2) && new Set(renderedPaths.map((values) => values[0])).size === 1;
|
|
2301
|
+
if (hasSingleWrongCasedAnchor) {
|
|
2221
2302
|
context.report({
|
|
2222
2303
|
node,
|
|
2223
|
-
message: `
|
|
2304
|
+
message: `data-testid for smart component "${component.name}" must be PascalCase: expected "${expected2}".`
|
|
2224
2305
|
});
|
|
2225
2306
|
continue;
|
|
2226
2307
|
}
|
|
2227
|
-
if (value === void 0 || value === expected) continue;
|
|
2228
|
-
if (component.smart ? !isPascalCase5(value) || value !== expected : !isKebabCase3(value)) {
|
|
2229
|
-
context.report({
|
|
2230
|
-
node,
|
|
2231
|
-
message: `data-testid for ${component.smart ? "smart" : "dumb"} component "${component.name}" must be ${component.smart ? "PascalCase" : "kebab-case"}: expected "${expected}".`
|
|
2232
|
-
});
|
|
2233
|
-
}
|
|
2234
|
-
} else if (component.smart) {
|
|
2235
2308
|
context.report({
|
|
2236
2309
|
node,
|
|
2237
|
-
message: `Smart component "${component.name}"
|
|
2310
|
+
message: `Smart component "${component.name}" must expose exactly one stable data-testid="${expected2}" anchor for itself in every rendered result. Place it on the existing DOM surface or on a child component that forwards data-testid instead of adding an artificial wrapper. See docs/next-codebase-guide/rules/smart-vs-dumb-component-rule.md`
|
|
2311
|
+
});
|
|
2312
|
+
continue;
|
|
2313
|
+
}
|
|
2314
|
+
const root = findSimpleRoot(component, text, filename);
|
|
2315
|
+
if (!root) continue;
|
|
2316
|
+
const { value } = getTestId(root);
|
|
2317
|
+
const expected = toKebabCase(component.name);
|
|
2318
|
+
if (value === void 0 || value === expected) continue;
|
|
2319
|
+
if (!isKebabCase3(value)) {
|
|
2320
|
+
context.report({
|
|
2321
|
+
node,
|
|
2322
|
+
message: `data-testid for dumb component "${component.name}" must be kebab-case: expected "${expected}".`
|
|
2238
2323
|
});
|
|
2239
2324
|
}
|
|
2240
2325
|
}
|
|
@@ -4837,7 +4922,7 @@ var schemaCasingRule = {
|
|
|
4837
4922
|
|
|
4838
4923
|
// eslint/rules/component-casing.ts
|
|
4839
4924
|
import path42 from "path";
|
|
4840
|
-
function
|
|
4925
|
+
function isPascalCase5(name) {
|
|
4841
4926
|
return /^[A-Z][A-Za-z0-9]*$/.test(name);
|
|
4842
4927
|
}
|
|
4843
4928
|
var componentCasingRule = {
|
|
@@ -4856,7 +4941,7 @@ var componentCasingRule = {
|
|
|
4856
4941
|
return {
|
|
4857
4942
|
Program(node) {
|
|
4858
4943
|
for (const declaration of findJsxReturningDeclarations(context.sourceCode.text, filename)) {
|
|
4859
|
-
if (
|
|
4944
|
+
if (isPascalCase5(declaration.name)) continue;
|
|
4860
4945
|
context.report({
|
|
4861
4946
|
node,
|
|
4862
4947
|
loc: { line: declaration.line, column: declaration.column },
|