pasika 0.10.22 → 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.
@@ -59,6 +59,7 @@ import path from "path";
59
59
  import ts from "typescript";
60
60
  var isPascalCase = (name) => /^[A-Z][A-Za-z0-9]*$/.test(name);
61
61
  var isHookCallName = (name) => /^use[A-Z]/.test(name);
62
+ var isHandlerName = (name) => /^handle[A-Z]/.test(name);
62
63
  function isExported(node) {
63
64
  if (!ts.canHaveModifiers(node)) return false;
64
65
  return (ts.getModifiers(node) ?? []).some((modifier) => modifier.kind === ts.SyntaxKind.ExportKeyword);
@@ -87,35 +88,77 @@ function containsJsx(node) {
87
88
  ts.forEachChild(node, visit);
88
89
  return found;
89
90
  }
90
- function containsSmartCall(node) {
91
+ function containsDataFetch(node) {
91
92
  let found = false;
92
93
  const visit = (child) => {
93
94
  if (found) return;
94
- if (ts.isCallExpression(child)) {
95
- const expression = child.expression;
96
- let name = "";
97
- if (ts.isIdentifier(expression)) {
98
- name = expression.text;
99
- } else if (ts.isPropertyAccessExpression(expression)) {
100
- name = expression.name.text;
101
- }
102
- if (isHookCallName(name)) {
103
- found = true;
104
- return;
105
- }
95
+ if (ts.isCallExpression(child) && ts.isIdentifier(child.expression) && child.expression.text === "zodFetch") {
96
+ found = true;
97
+ return;
106
98
  }
107
99
  ts.forEachChild(child, visit);
108
100
  };
109
101
  ts.forEachChild(node, visit);
110
102
  return found;
111
103
  }
112
- function isAsyncFunction(node) {
113
- return (ts.getModifiers(node) ?? []).some((modifier) => modifier.kind === ts.SyntaxKind.AsyncKeyword);
104
+ function isComponentTagName(tagName2) {
105
+ if (ts.isIdentifier(tagName2)) return isPascalCase(tagName2.text);
106
+ if (ts.isPropertyAccessExpression(tagName2)) {
107
+ let root = tagName2.expression;
108
+ while (ts.isPropertyAccessExpression(root)) root = root.expression;
109
+ return ts.isIdentifier(root) && isPascalCase(root.text);
110
+ }
111
+ return false;
112
+ }
113
+ function containsOwnedHandlerProp(node) {
114
+ const handlerNames = /* @__PURE__ */ new Set();
115
+ const collectHandlers = (child) => {
116
+ if (ts.isVariableDeclaration(child) && ts.isIdentifier(child.name) && isHandlerName(child.name.text) && child.initializer && (ts.isArrowFunction(child.initializer) || ts.isFunctionExpression(child.initializer) || ts.isCallExpression(child.initializer) && ts.isPropertyAccessExpression(child.initializer.expression) && child.initializer.expression.name.text === "useCallback" || ts.isCallExpression(child.initializer) && ts.isIdentifier(child.initializer.expression) && child.initializer.expression.text === "useCallback")) {
117
+ handlerNames.add(child.name.text);
118
+ } else if (ts.isFunctionDeclaration(child) && child.name && isHandlerName(child.name.text)) {
119
+ handlerNames.add(child.name.text);
120
+ }
121
+ ts.forEachChild(child, collectHandlers);
122
+ };
123
+ ts.forEachChild(node, collectHandlers);
124
+ if (handlerNames.size === 0) return false;
125
+ let found = false;
126
+ const findPassedHandler = (child) => {
127
+ if (found) return;
128
+ let attributes;
129
+ let tagName2;
130
+ if (ts.isJsxElement(child)) {
131
+ attributes = child.openingElement.attributes;
132
+ tagName2 = child.openingElement.tagName;
133
+ } else if (ts.isJsxSelfClosingElement(child)) {
134
+ attributes = child.attributes;
135
+ tagName2 = child.tagName;
136
+ }
137
+ if (attributes && tagName2 && isComponentTagName(tagName2)) {
138
+ for (const property of attributes.properties) {
139
+ if (!ts.isJsxAttribute(property) || !ts.isIdentifier(property.name) || !/^on[A-Z]/.test(property.name.text)) {
140
+ continue;
141
+ }
142
+ const initializer = property.initializer;
143
+ if (!initializer || !ts.isJsxExpression(initializer) || !initializer.expression) continue;
144
+ if (ts.isIdentifier(initializer.expression) && handlerNames.has(initializer.expression.text)) {
145
+ found = true;
146
+ return;
147
+ }
148
+ }
149
+ }
150
+ ts.forEachChild(child, findPassedHandler);
151
+ };
152
+ ts.forEachChild(node, findPassedHandler);
153
+ return found;
154
+ }
155
+ function isSmartComponent(node) {
156
+ return containsDataFetch(node) || containsOwnedHandlerProp(node);
114
157
  }
115
158
  function componentFromFunction(node) {
116
159
  const name = node.name?.text;
117
160
  if (!name || !isPascalCase(name) || !containsJsx(node)) return void 0;
118
- return { name, declaration: node, smart: containsSmartCall(node) || isAsyncFunction(node) };
161
+ return { name, declaration: node, smart: isSmartComponent(node) };
119
162
  }
120
163
  function componentFromVariable(node) {
121
164
  if (!ts.isIdentifier(node.name) || !isPascalCase(node.name.text)) return void 0;
@@ -125,7 +168,7 @@ function componentFromVariable(node) {
125
168
  return {
126
169
  name: node.name.text,
127
170
  declaration: node,
128
- smart: containsSmartCall(initializer)
171
+ smart: isSmartComponent(initializer)
129
172
  };
130
173
  }
131
174
  function parseComponentInfo(text, filename, options) {
@@ -221,6 +264,86 @@ function findSimpleRoot(component, _text, _filename) {
221
264
  if (roots.some((root) => root.tagName !== firstTag)) return void 0;
222
265
  return roots[0];
223
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
+ }
224
347
  function getTestId(root) {
225
348
  const attribute = root.attributes.find(
226
349
  (candidate) => ts.isJsxAttribute(candidate) && ts.isIdentifier(candidate.name) && candidate.name.text === "data-testid"
@@ -2150,7 +2273,6 @@ var NEXT_ROUTING_FILES2 = /* @__PURE__ */ new Set([
2150
2273
  "sitemap",
2151
2274
  "twitter-image"
2152
2275
  ]);
2153
- var isPascalCase5 = (value) => /^[A-Z][A-Za-z0-9]*$/.test(value);
2154
2276
  var isKebabCase3 = (value) => /^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/.test(value);
2155
2277
  var dataTestIdCaseRule = {
2156
2278
  meta: {
@@ -2170,28 +2292,34 @@ var dataTestIdCaseRule = {
2170
2292
  return {
2171
2293
  Program(node) {
2172
2294
  for (const component of components) {
2173
- const root = findSimpleRoot(component, text, filename);
2174
- if (root) {
2175
- const { value } = getTestId(root);
2176
- const expected = component.smart ? component.name : toKebabCase(component.name);
2177
- if (component.smart && value === void 0) {
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) {
2178
2302
  context.report({
2179
2303
  node,
2180
- message: `Smart component "${component.name}" with one outer DOM element must set data-testid="${expected}".`
2304
+ message: `data-testid for smart component "${component.name}" must be PascalCase: expected "${expected2}".`
2181
2305
  });
2182
2306
  continue;
2183
2307
  }
2184
- if (value === void 0 || value === expected) continue;
2185
- if (component.smart ? !isPascalCase5(value) || value !== expected : !isKebabCase3(value)) {
2186
- context.report({
2187
- node,
2188
- message: `data-testid for ${component.smart ? "smart" : "dumb"} component "${component.name}" must be ${component.smart ? "PascalCase" : "kebab-case"}: expected "${expected}".`
2189
- });
2190
- }
2191
- } else if (component.smart) {
2192
2308
  context.report({
2193
2309
  node,
2194
- message: `Smart component "${component.name}" has no single outer element; wrap its content in one outer element with data-testid="${component.name}" instead of rendering multiple roots. See docs/next-codebase-guide/rules/smart-vs-dumb-component-rule.md`
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}".`
2195
2323
  });
2196
2324
  }
2197
2325
  }
@@ -4794,7 +4922,7 @@ var schemaCasingRule = {
4794
4922
 
4795
4923
  // eslint/rules/component-casing.ts
4796
4924
  import path42 from "path";
4797
- function isPascalCase6(name) {
4925
+ function isPascalCase5(name) {
4798
4926
  return /^[A-Z][A-Za-z0-9]*$/.test(name);
4799
4927
  }
4800
4928
  var componentCasingRule = {
@@ -4813,7 +4941,7 @@ var componentCasingRule = {
4813
4941
  return {
4814
4942
  Program(node) {
4815
4943
  for (const declaration of findJsxReturningDeclarations(context.sourceCode.text, filename)) {
4816
- if (isPascalCase6(declaration.name)) continue;
4944
+ if (isPascalCase5(declaration.name)) continue;
4817
4945
  context.report({
4818
4946
  node,
4819
4947
  loc: { line: declaration.line, column: declaration.column },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pasika",
3
- "version": "0.10.22",
3
+ "version": "0.10.24",
4
4
  "description": "Reusable agent setup package",
5
5
  "repository": {
6
6
  "type": "git",