@bigbinary/eslint-plugin-neeto 1.1.21 → 1.1.23

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.
Files changed (3) hide show
  1. package/README.md +3 -0
  2. package/dist/index.js +366 -5
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -46,6 +46,9 @@ module.exports = {
46
46
 
47
47
  ## Exported rules
48
48
 
49
+
50
+ - [use-classnames-for-dynamic-classes](./docs/src/rules/use-classnames-for-dynamic-classes.md)
51
+ - [use-use-mutation-with-invalidation-hook](./docs/src/rules/use-use-mutation-with-invalidation-hook.md)
49
52
  - [no-withT-for-smart-components](./docs/src/rules/no-withT-for-smart-components.md)
50
53
  - [no-large-functions-inside-jsx](./docs/src/rules/no-large-functions-inside-jsx.md)
51
54
  - [use-standard-date-time-formats](./docs/src/rules/use-standard-date-time-formats.md)
package/dist/index.js CHANGED
@@ -42,6 +42,8 @@
42
42
  var commonResolve$2 = loadJS$2("node_modules/@bigbinary/neeto-commons-frontend/configs/webpack/resolve.js");
43
43
  var resolve$2 = ramda.mergeDeepLeft(projectResolve$2, commonResolve$2);
44
44
  var rules = {
45
+ "@bigbinary/neeto/use-classnames-for-dynamic-classes": "error",
46
+ "@bigbinary/neeto/use-use-mutation-with-invalidation-hook": "error",
45
47
  "@bigbinary/neeto/no-withT-for-smart-components": "error",
46
48
  "@bigbinary/neeto/no-large-functions-inside-jsx": "error",
47
49
  "@bigbinary/neeto/use-standard-date-time-formats": "error",
@@ -549,6 +551,8 @@
549
551
  var NO_WITH_T_IN_SMART_COMPONENTS = '"withT" HOC should only be used in dumb components. "useTranslation" hook should be used here.';
550
552
  var USE_STANDARD_DATE_TIME_FORMATS = 'Use "dateFormat/timeFormat" from "@bigbinary/neeto-commons-frontend/utils" instead. Refer: https://github.com/bigbinary/neeto-commons-frontend/blob/main/docs/utils/README.md#dateformattimeformat';
551
553
  var MOVE_SPREAD_ATTRIBUTES_TO_TOP = "Move spread attribute to top";
554
+ var USE_USE_MUTATION_WITH_INVALIDATION = "Use useMutationWithInvalidation from 'neetocommons/react-utils'";
555
+ var USE_CLASSNAMES_PACKAGE = "Use classnames package for dynamic classes";
552
556
 
553
557
  var PACKAGES_TO_CHECK = ["@bigbinary/neeto-molecules", "@bigbinary/neetoui", "@bigbinary/neeto-commons-frontend"];
554
558
 
@@ -5368,7 +5372,7 @@
5368
5372
  };
5369
5373
 
5370
5374
  //import { } from "neetocommons/react-utils";
5371
- var REACT_UTILS_IMPORT_PATTERN$1 = {
5375
+ var REACT_UTILS_IMPORT_PATTERN$2 = {
5372
5376
  type: TYPES.IMPORT_DECLARATION,
5373
5377
  source: {
5374
5378
  type: TYPES.LITERAL,
@@ -5478,7 +5482,7 @@
5478
5482
  context: context,
5479
5483
  fixer: fixer,
5480
5484
  functions: ["handleMetaClick"],
5481
- importPattern: REACT_UTILS_IMPORT_PATTERN$1
5485
+ importPattern: REACT_UTILS_IMPORT_PATTERN$2
5482
5486
  });
5483
5487
  if (importStatement) fixerArray.push(importStatement);
5484
5488
  return fixerArray;
@@ -6648,7 +6652,7 @@
6648
6652
  var restOfString = inputString.slice(position + 1);
6649
6653
  return firstCharacter + textToInsert + restOfString;
6650
6654
  };
6651
- var reportError = function reportError(_ref) {
6655
+ var reportError$1 = function reportError(_ref) {
6652
6656
  var context = _ref.context,
6653
6657
  node = _ref.node,
6654
6658
  arrowFunctionExpression = _ref.arrowFunctionExpression,
@@ -6689,7 +6693,7 @@
6689
6693
  componentDeclaration = node;
6690
6694
  if (!pure.matches(WITH_T_HOC_PATTERN, node)) return;
6691
6695
  var arrowFunctionExpression = node.declarations[0].init.arguments[0];
6692
- reportError({
6696
+ reportError$1({
6693
6697
  context: context,
6694
6698
  node: node,
6695
6699
  arrowFunctionExpression: arrowFunctionExpression
@@ -6698,7 +6702,7 @@
6698
6702
  ExportDefaultDeclaration: function ExportDefaultDeclaration(node) {
6699
6703
  if (!pure.matches(EXPORTED_WITH_T_PATTERN, node)) return;
6700
6704
  if (!pure.matches(SMART_COMPONENT_PATTERN, componentDeclaration)) return;
6701
- reportError({
6705
+ reportError$1({
6702
6706
  context: context,
6703
6707
  node: node,
6704
6708
  arrowFunctionExpression: componentDeclaration.declarations[0].init,
@@ -8412,6 +8416,99 @@
8412
8416
  }
8413
8417
  };
8414
8418
 
8419
+ // className={isPending ? "opacity-30" : "opacity-0"}
8420
+ // className={isPending && "opacity-30"}
8421
+ var CONDITIONAL_CLASSNAMES_PATTERN = {
8422
+ name: {
8423
+ type: TYPES.JSX_IDENTIFIER,
8424
+ name: "className"
8425
+ },
8426
+ value: {
8427
+ type: TYPES.JSX_EXPRESSION_CONTAINER,
8428
+ expression: {
8429
+ type: ramda.includes(ramda.__, [TYPES.LOGICAL_EXPRESSION, TYPES.CONDITIONAL_EXPRESSION, TYPES.TEMPLATE_LITERAL])
8430
+ }
8431
+ }
8432
+ };
8433
+
8434
+ // import classnames from "classnames";
8435
+ var CLASSNAMES_IMPORT_PATTERN = {
8436
+ type: TYPES.IMPORT_DECLARATION,
8437
+ source: {
8438
+ type: TYPES.LITERAL,
8439
+ value: "classnames"
8440
+ },
8441
+ specifiers: pure.existsBy({
8442
+ type: TYPES.IMPORT_DEFAULT_SPECIFIER,
8443
+ local: {
8444
+ type: TYPES.IDENTIFIER,
8445
+ name: "classnames"
8446
+ }
8447
+ })
8448
+ };
8449
+
8450
+ // ${isPending && "opacity-30"}
8451
+ // ${isPending ? "opacity-30" : "opacity-0"}
8452
+ var CONDITIONAL_EXPRESSION_PATTERN = {
8453
+ expressions: [{
8454
+ type: ramda.includes(ramda.__, [TYPES.LOGICAL_EXPRESSION, TYPES.CONDITIONAL_EXPRESSION])
8455
+ }]
8456
+ };
8457
+
8458
+ var getFixString = function getFixString(context, expressionNode) {
8459
+ if (expressionNode.type === TYPES.LOGICAL_EXPRESSION) {
8460
+ var left = context.getSourceCode().getText(expressionNode.left);
8461
+ var right = context.getSourceCode().getText(expressionNode.right);
8462
+ return "classnames({ ".concat(right, ": ").concat(left, " })");
8463
+ }
8464
+ var test = context.getSourceCode().getText(expressionNode.test);
8465
+ var consequent = context.getSourceCode().getText(expressionNode.consequent);
8466
+ var alternate = context.getSourceCode().getText(expressionNode.alternate);
8467
+ return "classnames({".concat(consequent, ": ").concat(test, ", ").concat(alternate, ": !(").concat(test, ")})");
8468
+ };
8469
+
8470
+ var useClassnamesForDynamicClasses = {
8471
+ meta: {
8472
+ type: "error",
8473
+ docs: {
8474
+ url: "https://github.com/bigbinary/eslint-plugin-neeto/blob/main/docs/src/rules/use-classnames-for-dynamic-classes.md",
8475
+ description: "Enforce use of classnames for dynamic classes"
8476
+ },
8477
+ fixable: "code"
8478
+ },
8479
+ create: function create(context) {
8480
+ return {
8481
+ JSXAttribute: function JSXAttribute(node) {
8482
+ if (!pure.matches(CONDITIONAL_CLASSNAMES_PATTERN, node)) return;
8483
+ var expressionNode = node.value.expression;
8484
+ if (expressionNode.type === TYPES.TEMPLATE_LITERAL) {
8485
+ if (pure.matches(CONDITIONAL_EXPRESSION_PATTERN, expressionNode)) {
8486
+ context.report({
8487
+ node: node,
8488
+ message: USE_CLASSNAMES_PACKAGE
8489
+ });
8490
+ }
8491
+ return;
8492
+ }
8493
+ context.report({
8494
+ node: node,
8495
+ message: USE_CLASSNAMES_PACKAGE,
8496
+ fix: function fix(fixer) {
8497
+ var fixString = getFixString(context, expressionNode);
8498
+ var fixerArray = [fixer.replaceText(expressionNode, fixString)];
8499
+ var importExists = pure.existsBy(CLASSNAMES_IMPORT_PATTERN, getAllImports(node));
8500
+ if (!importExists) {
8501
+ var addImport = fixer.insertTextBefore(context.getAncestors()[0], 'import classnames from "classnames";\n');
8502
+ fixerArray.push(addImport);
8503
+ }
8504
+ return fixerArray;
8505
+ }
8506
+ });
8507
+ }
8508
+ };
8509
+ }
8510
+ };
8511
+
8415
8512
  var IMPORT_SOURCES = {
8416
8513
  "neeto-commons-frontend/constants": ["neetocommons/constants", "@bigbinary/neeto-commons-frontend/constants"],
8417
8514
  "neeto-team-members-frontend": ["neetoteam", "@bigbinary/neeto-team-members-frontend"]
@@ -26298,6 +26395,268 @@
26298
26395
  }
26299
26396
  };
26300
26397
 
26398
+ var ONSUCCESS_OPTION = {
26399
+ type: TYPES.PROPERTY,
26400
+ key: {
26401
+ name: "onSuccess"
26402
+ }
26403
+ };
26404
+
26405
+ // keysToInvalidate: [...]
26406
+ var KEYS_TO_INVALIDATE_OPTION = {
26407
+ type: TYPES.PROPERTY,
26408
+ key: {
26409
+ name: "keysToInvalidate"
26410
+ }
26411
+ };
26412
+
26413
+ // {
26414
+ // onSuccess: (...) => {....},
26415
+ // ...,
26416
+ // });
26417
+ var OPTIONS = {
26418
+ type: TYPES.OBJECT_EXPRESSION,
26419
+ properties: pure.existsBy(ONSUCCESS_OPTION)
26420
+ };
26421
+
26422
+ // useMutation(api.update, {
26423
+ // onSuccess: (...) => {....},
26424
+ // ...,
26425
+ // });
26426
+ var USE_MUTATION_HOOK_WITH_ONSUCCESS = {
26427
+ callee: {
26428
+ type: TYPES.IDENTIFIER,
26429
+ name: ramda.includes(ramda.__, ["useMutation", "useMutationWithInvalidation"])
26430
+ },
26431
+ arguments: pure.existsBy(OPTIONS)
26432
+ };
26433
+
26434
+ // useMutation(api.update, {
26435
+ // onSuccess: (...) => {....queryClient....},
26436
+ // ...,
26437
+ // });
26438
+ var INVALIDATION_PATTERN = {
26439
+ type: TYPES.EXPRESSION_STATEMENT,
26440
+ expression: {
26441
+ type: TYPES.CALL_EXPRESSION,
26442
+ callee: {
26443
+ type: TYPES.MEMBER_EXPRESSION,
26444
+ object: {
26445
+ type: TYPES.IDENTIFIER,
26446
+ name: "queryClient"
26447
+ }
26448
+ }
26449
+ }
26450
+ };
26451
+
26452
+ // () => queryClient.invalidateQueries(CUSTOMERS)
26453
+ var SINGLE_INVALIDATION_PATTERN = {
26454
+ type: TYPES.CALL_EXPRESSION,
26455
+ callee: {
26456
+ type: TYPES.MEMBER_EXPRESSION,
26457
+ object: {
26458
+ type: TYPES.IDENTIFIER,
26459
+ name: "queryClient"
26460
+ },
26461
+ property: {
26462
+ type: TYPES.IDENTIFIER,
26463
+ name: "invalidateQueries"
26464
+ }
26465
+ }
26466
+ };
26467
+
26468
+ // import { useMutationWithInvalidation } from "neetocommons/react-utils";
26469
+ var USE_MUTATION_WITH_INVALIDATION_IMPORT_PATTERN = {
26470
+ type: TYPES.IMPORT_DECLARATION,
26471
+ specifiers: pure.existsBy({
26472
+ type: TYPES.IMPORT_SPECIFIER,
26473
+ imported: {
26474
+ type: TYPES.IDENTIFIER,
26475
+ name: "useMutationWithInvalidation"
26476
+ }
26477
+ }),
26478
+ source: {
26479
+ type: TYPES.LITERAL,
26480
+ value: "neetocommons/react-utils"
26481
+ }
26482
+ };
26483
+
26484
+ //import { } from "neetocommons/react-utils";
26485
+ var REACT_UTILS_IMPORT_PATTERN$1 = {
26486
+ type: TYPES.IMPORT_DECLARATION,
26487
+ source: {
26488
+ type: TYPES.LITERAL,
26489
+ value: "neetocommons/react-utils"
26490
+ },
26491
+ specifiers: [{
26492
+ type: TYPES.IMPORT_SPECIFIER
26493
+ }]
26494
+ };
26495
+
26496
+ // queryClient.setQueryData(...);
26497
+ var OTHER_QUERY_CLIENT_USAGE_PATTERN = {
26498
+ callee: {
26499
+ property: {
26500
+ type: TYPES.IDENTIFIER,
26501
+ name: pure.isNot("invalidateQueries")
26502
+ }
26503
+ }
26504
+ };
26505
+
26506
+ var reportError = function reportError(context, node) {
26507
+ context.report({
26508
+ node: node,
26509
+ message: USE_USE_MUTATION_WITH_INVALIDATION
26510
+ });
26511
+ };
26512
+ var getInitialFixerArray = function getInitialFixerArray(_ref) {
26513
+ var node = _ref.node,
26514
+ context = _ref.context,
26515
+ fixer = _ref.fixer,
26516
+ mutationOptions = _ref.mutationOptions,
26517
+ existingKeysToInvalidate = _ref.existingKeysToInvalidate,
26518
+ keys = _ref.keys,
26519
+ importExists = _ref.importExists;
26520
+ var replaceCallee = fixer.replaceText(node.callee, "useMutationWithInvalidation");
26521
+ var indentLevel = context.getSourceCode().getFirstToken(mutationOptions.properties[0]).loc.start.column;
26522
+ var addKeysToInvalidate;
26523
+ if (existingKeysToInvalidate) {
26524
+ addKeysToInvalidate = fixer.insertTextAfter(ramda.last(existingKeysToInvalidate.value.elements), ",".concat(keys));
26525
+ } else {
26526
+ addKeysToInvalidate = fixer.insertTextBefore(mutationOptions.properties[0], "keysToInvalidate: [".concat(keys, "],\n").concat(" ".repeat(indentLevel)));
26527
+ }
26528
+ var fixerArray = [replaceCallee, addKeysToInvalidate];
26529
+ if (!importExists) {
26530
+ var addImport = addFunctionImport({
26531
+ context: context,
26532
+ fixer: fixer,
26533
+ functions: ["useMutationWithInvalidation"],
26534
+ importPattern: REACT_UTILS_IMPORT_PATTERN$1
26535
+ });
26536
+ fixerArray.push(addImport);
26537
+ }
26538
+ return fixerArray;
26539
+ };
26540
+ var getRemoveCommaFix = function getRemoveCommaFix(context, fixer, onSuccess) {
26541
+ var tokenAfter = context.getTokenAfter(onSuccess);
26542
+ return tokenAfter.value === "," ? fixer.remove(tokenAfter) : null;
26543
+ };
26544
+ var getVariables = function getVariables(context, params) {
26545
+ var _params$arguments$0$e;
26546
+ var variables = (_params$arguments$0$e = params.arguments[0].elements) === null || _params$arguments$0$e === void 0 ? void 0 : _params$arguments$0$e.filter(function (element) {
26547
+ return element.type !== TYPES.LITERAL && /[a-z]/.test(context.getSourceCode().getText(element));
26548
+ });
26549
+ if (!params.arguments[0].elements && /[a-z]/.test(context.getSourceCode().getText(params.arguments[0]))) {
26550
+ variables = params.arguments[0];
26551
+ }
26552
+ return variables;
26553
+ };
26554
+
26555
+ var useUseMutationWithInvalidationHook = {
26556
+ meta: {
26557
+ type: "problem",
26558
+ docs: {
26559
+ url: "https://github.com/bigbinary/eslint-plugin-neeto/blob/main/docs/src/rules/use-use-mutation-with-invalidation-hook.md",
26560
+ description: "Enforce use of useMutationWithInvalidation"
26561
+ },
26562
+ fixable: "code"
26563
+ },
26564
+ create: function create(context) {
26565
+ return {
26566
+ CallExpression: function CallExpression(node) {
26567
+ if (!pure.matches(USE_MUTATION_HOOK_WITH_ONSUCCESS, node)) return;
26568
+ var mutationOptions = pure.findBy({
26569
+ type: TYPES.OBJECT_EXPRESSION
26570
+ }, node.arguments);
26571
+ var onSuccess = pure.findBy(ONSUCCESS_OPTION, mutationOptions.properties);
26572
+ var existingKeysToInvalidate = pure.findBy(KEYS_TO_INVALIDATE_OPTION, mutationOptions.properties);
26573
+ if (!onSuccess.value.body) return;
26574
+ var importExists = pure.existsBy(USE_MUTATION_WITH_INVALIDATION_IMPORT_PATTERN, getAllImports(node));
26575
+ if (onSuccess.value.body.type === TYPES.CALL_EXPRESSION) {
26576
+ var params = onSuccess.value.body;
26577
+ if (!pure.matches(SINGLE_INVALIDATION_PATTERN, params)) return;
26578
+ var variables = getVariables(context, params);
26579
+ if (pure.isPresent(variables)) {
26580
+ reportError(context, node);
26581
+ } else {
26582
+ context.report({
26583
+ node: node,
26584
+ message: USE_USE_MUTATION_WITH_INVALIDATION,
26585
+ fix: function fix(fixer) {
26586
+ var fixerArray = getInitialFixerArray({
26587
+ node: node,
26588
+ context: context,
26589
+ fixer: fixer,
26590
+ mutationOptions: mutationOptions,
26591
+ existingKeysToInvalidate: existingKeysToInvalidate,
26592
+ keys: context.getSourceCode().getText(params.arguments[0]),
26593
+ importExists: importExists
26594
+ });
26595
+ var removeInvalidations = fixer.remove(onSuccess);
26596
+ fixerArray.push(removeInvalidations);
26597
+ var removeComma = getRemoveCommaFix(context, fixer, onSuccess);
26598
+ if (removeComma) fixerArray.push(removeComma);
26599
+ return fixerArray;
26600
+ }
26601
+ });
26602
+ }
26603
+ } else if (onSuccess.value.body.type === TYPES.BLOCK_STATEMENT) {
26604
+ var _params = onSuccess.value.body.body;
26605
+ var invalidations = pure._filterBy(INVALIDATION_PATTERN, _params);
26606
+ if (pure.isNotPresent(invalidations)) return;
26607
+ var otherOptions = pure._removeBy(INVALIDATION_PATTERN, _params);
26608
+ var queryClientInOtherOptions = otherOptions === null || otherOptions === void 0 ? void 0 : otherOptions.find(function (item) {
26609
+ return context.getSourceCode().getText(item).includes("queryClient");
26610
+ });
26611
+ if (queryClientInOtherOptions || pure.findBy(OTHER_QUERY_CLIENT_USAGE_PATTERN, ramda.pluck("expression", invalidations))) {
26612
+ return;
26613
+ }
26614
+ var invalidationsWithoutVariables = invalidations.filter(function (item) {
26615
+ var variables = getVariables(context, item.expression);
26616
+ return pure.isNotPresent(variables);
26617
+ });
26618
+ if (pure.isNotPresent(invalidationsWithoutVariables)) {
26619
+ reportError(context, node);
26620
+ } else {
26621
+ var keys = ramda.pluck("expression", invalidationsWithoutVariables).flatMap(function (item) {
26622
+ return item.arguments.map(function (arg) {
26623
+ return context.getSourceCode().getText(arg);
26624
+ });
26625
+ });
26626
+ context.report({
26627
+ node: node,
26628
+ message: USE_USE_MUTATION_WITH_INVALIDATION,
26629
+ fix: function fix(fixer) {
26630
+ var fixerArray = getInitialFixerArray({
26631
+ node: node,
26632
+ context: context,
26633
+ fixer: fixer,
26634
+ mutationOptions: mutationOptions,
26635
+ existingKeysToInvalidate: existingKeysToInvalidate,
26636
+ keys: keys.join(", "),
26637
+ importExists: importExists
26638
+ });
26639
+ if (_params.length === invalidationsWithoutVariables.length) {
26640
+ var _removeInvalidations = fixer.remove(onSuccess);
26641
+ fixerArray.push(_removeInvalidations);
26642
+ var removeComma = getRemoveCommaFix(context, fixer, onSuccess);
26643
+ if (removeComma) fixerArray.push(removeComma);
26644
+ return fixerArray;
26645
+ }
26646
+ var removeInvalidations = invalidationsWithoutVariables.map(function (item) {
26647
+ return fixer.remove(item);
26648
+ });
26649
+ fixerArray.push.apply(fixerArray, _toConsumableArray(removeInvalidations));
26650
+ return fixerArray;
26651
+ }
26652
+ });
26653
+ }
26654
+ }
26655
+ }
26656
+ };
26657
+ }
26658
+ };
26659
+
26301
26660
  var useWebpackAlias = {
26302
26661
  meta: {
26303
26662
  type: "suggestion",
@@ -26881,6 +27240,8 @@
26881
27240
 
26882
27241
  var index = {
26883
27242
  rules: {
27243
+ "use-classnames-for-dynamic-classes": useClassnamesForDynamicClasses,
27244
+ "use-use-mutation-with-invalidation-hook": useUseMutationWithInvalidationHook,
26884
27245
  "no-withT-for-smart-components": noWithTForSmartComponents,
26885
27246
  "no-large-functions-inside-jsx": noLargeFunctionsInsideJsx,
26886
27247
  "use-standard-date-time-formats": useStandardDateTimeFormats,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bigbinary/eslint-plugin-neeto",
3
- "version": "1.1.21",
3
+ "version": "1.1.23",
4
4
  "main": "dist/index.js",
5
5
  "license": "UNLICENSED",
6
6
  "scripts": {