@bigbinary/eslint-plugin-neeto 1.7.5 → 1.8.0

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 +1 -0
  2. package/dist/index.js +253 -8
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -140,6 +140,7 @@ of the issues identified in JavaScript code.
140
140
  - [use-permissions-prop-in-private-route](./docs/src/rules/use-permissions-prop-in-private-route.md)
141
141
  - [use-webpack-alias](./docs/src/rules/use-webpack-alias.md)
142
142
  - [use-neetoui-action-block-in-formik-forms](./docs/src/rules/use-neetoui-action-block-in-formik-forms.md)
143
+ - [prefer-radio-buttons-for-few-options](./docs/src/rules/prefer-radio-buttons-for-few-options.md)
143
144
 
144
145
  ## Overriding rules
145
146
 
package/dist/index.js CHANGED
@@ -38,6 +38,7 @@
38
38
  var commonResolve$2 = loadJS$2("node_modules/@bigbinary/neeto-commons-frontend/configs/webpack/resolve.js");
39
39
  var resolve$2 = ramda.mergeDeepLeft(projectResolve$2, commonResolve$2);
40
40
  var rules = {
41
+ "@bigbinary/neeto/prefer-radio-buttons-for-few-options": "warn",
41
42
  "@bigbinary/neeto/switch-children-must-be-route-or-redirect": "error",
42
43
  "@bigbinary/neeto/use-dayjs-from-neeto-commons-fronted": "error",
43
44
  "@bigbinary/neeto/use-neetoui-action-block-in-formik-forms": "warn",
@@ -330,7 +331,7 @@
330
331
  }
331
332
 
332
333
  var name = "@bigbinary/eslint-plugin-neeto";
333
- var version = "1.7.4";
334
+ var version = "1.7.6";
334
335
  var main = "dist/index.js";
335
336
  var license = "UNLICENSED";
336
337
  var scripts = {
@@ -726,8 +727,23 @@
726
727
  var helpUrlConstantNamingConvention = function helpUrlConstantNamingConvention(filename) {
727
728
  return "All exported constants in ".concat(filename, " must follow the HELP_..._URL naming convention.");
728
729
  };
730
+ var baseUrlShouldBeInStandardFormat = function baseUrlShouldBeInStandardFormat(filename) {
731
+ return "All exported base URLS in ".concat(filename, " must be in the standard format help.neeto<product>.com.");
732
+ };
729
733
  var helpUrlInvalidType = function helpUrlInvalidType(filename) {
730
- return "Help URL constant must be a template literal string or assigned from another help URL constant. Objects and other types are not allowed in ".concat(filename, ".");
734
+ return "Help URL constant must be a template literal string, assigned from another help URL constant or a help base URL. Objects and other types are not allowed in ".concat(filename, ".");
735
+ };
736
+ var helpUrlOtherNeetoProductConstantNamingConvention = function helpUrlOtherNeetoProductConstantNamingConvention(_ref2) {
737
+ var filename = _ref2.filename,
738
+ constantName = _ref2.constantName,
739
+ suggestedConstantName = _ref2.suggestedConstantName;
740
+ return "All constants that export help URLs for other neeto products in ".concat(filename, " must follow the HELP_NEETO..._BASE_URL naming convention. Replace '").concat(constantName, "' with '").concat(suggestedConstantName, "'.");
741
+ };
742
+ var helpUrlOtherNeetoProductConstantShouldMatchHelpUrl = function helpUrlOtherNeetoProductConstantShouldMatchHelpUrl(_ref3) {
743
+ var filename = _ref3.filename,
744
+ constantName = _ref3.constantName,
745
+ suggestedConstantName = _ref3.suggestedConstantName;
746
+ return "All constants that export help URLs for other neeto products in ".concat(filename, " must match the help URL. Replace '").concat(constantName, "' with '").concat(suggestedConstantName, "'.");
731
747
  };
732
748
  var danglingHelpUrlConstant = function danglingHelpUrlConstant(constName, filename) {
733
749
  return "Help URL constant '".concat(constName, "' should be defined in '").concat(filename, "'.");
@@ -738,6 +754,7 @@
738
754
  var HELP_URL_SHOULD_USE_PERMALINK = "Help URL must use a permalink (/p/...) instead of an article link (/articles/...).";
739
755
  var HELP_URL_INCORRECT_FORMAT = "Incorrect help URL format. Please use the 'help.neeto<product>.com' format instead of 'neeto<product>help.neetokb.com'.";
740
756
  var INVALID_SWITCH_CHILD_MESSAGE = "Switch component can only contain Route, Redirect, or PrivateRoute as direct children";
757
+ var PREFER_RADIO_BUTTONS_FOR_FEW_OPTIONS = "Consider using Radio buttons instead of Dropdown/Select when there are fewer than 4 options.";
741
758
 
742
759
  var PACKAGES_TO_CHECK = ["@bigbinary/neeto-molecules", "@bigbinary/neetoui", "@bigbinary/neeto-commons-frontend"];
743
760
 
@@ -9159,6 +9176,144 @@
9159
9176
  }
9160
9177
  };
9161
9178
 
9179
+ var MIN_OPTIONS_THRESHOLD = 4;
9180
+ var DROPDOWN_COMPONENT_NAMES = ["Dropdown", "Select", "DropdownMenu", "SelectInput", "MultiSelect", "Menu"];
9181
+ var OPTION_ELEMENT_NAMES = ["option", "Option", "MenuItem", "MenuItemButton"];
9182
+ var DROPDOWN_COMPONENT_PATTERN = {
9183
+ type: TYPES.JSX_ELEMENT,
9184
+ openingElement: {
9185
+ type: TYPES.JSX_OPENING_ELEMENT,
9186
+ name: {
9187
+ type: TYPES.JSX_IDENTIFIER,
9188
+ name: ramda.includes(ramda.__, DROPDOWN_COMPONENT_NAMES)
9189
+ }
9190
+ }
9191
+ };
9192
+ var OPTION_ELEMENT_PATTERN = {
9193
+ type: TYPES.JSX_ELEMENT,
9194
+ openingElement: {
9195
+ type: TYPES.JSX_OPENING_ELEMENT,
9196
+ name: {
9197
+ type: TYPES.JSX_IDENTIFIER,
9198
+ name: ramda.includes(ramda.__, OPTION_ELEMENT_NAMES)
9199
+ }
9200
+ }
9201
+ };
9202
+ var OPTIONS_ARRAY_PATTERN = {
9203
+ type: TYPES.JSX_ATTRIBUTE,
9204
+ name: {
9205
+ type: TYPES.JSX_IDENTIFIER,
9206
+ name: "options"
9207
+ },
9208
+ value: {
9209
+ type: TYPES.JSX_EXPRESSION_CONTAINER
9210
+ }
9211
+ };
9212
+ var SPREAD_ATTRIBUTE_PATTERN = {
9213
+ type: TYPES.JSX_SPREAD_ATTRIBUTE,
9214
+ argument: {
9215
+ type: TYPES.OBJECT_EXPRESSION
9216
+ }
9217
+ };
9218
+ var SPREAD_OPTION_PROPERTY_PATTERN = {
9219
+ type: TYPES.PROPERTY,
9220
+ key: {
9221
+ type: TYPES.IDENTIFIER,
9222
+ name: "options"
9223
+ }
9224
+ };
9225
+
9226
+ var isImportedVariable = function isImportedVariable(node, context) {
9227
+ if (node.type !== TYPES.IDENTIFIER) return false;
9228
+ var scope = context.sourceCode.getScope(node);
9229
+ var variable = scope.set.get(node.name);
9230
+ if (!variable || ramda.isEmpty(variable.defs)) return false;
9231
+ var def = variable.defs[0];
9232
+ return def.type === "ImportBinding";
9233
+ };
9234
+ var resolveVariableReference = function resolveVariableReference(node, context) {
9235
+ var _def$node;
9236
+ if (node.type !== TYPES.IDENTIFIER) return null;
9237
+ var scope = context.sourceCode.getScope(node);
9238
+ var variable = scope.set.get(node.name);
9239
+ if (!variable || ramda.isEmpty(variable.defs)) return null;
9240
+ var def = variable.defs[0];
9241
+ return ((_def$node = def.node) === null || _def$node === void 0 ? void 0 : _def$node.init) || null;
9242
+ };
9243
+ var countArrayElements = function countArrayElements(node, context) {
9244
+ if (node.type === TYPES.ARRAY_EXPRESSION) {
9245
+ return node.elements ? node.elements.length : 0;
9246
+ }
9247
+ if (node.type === TYPES.IDENTIFIER) {
9248
+ if (isImportedVariable(node, context)) return -1;
9249
+ var resolvedNode = resolveVariableReference(node, context);
9250
+ if (resolvedNode) {
9251
+ return countArrayElements(resolvedNode, context);
9252
+ }
9253
+ }
9254
+ return 0;
9255
+ };
9256
+ var countOptions = function countOptions(node, context) {
9257
+ var _node$openingElement, _optionsAttribute$val;
9258
+ var optionCount = 0;
9259
+
9260
+ // Count direct option children
9261
+ if (node.children) {
9262
+ var optionElements = node.children.filter(function (child) {
9263
+ return neetoCist.matches(OPTION_ELEMENT_PATTERN, child);
9264
+ });
9265
+ optionCount += optionElements.length;
9266
+ }
9267
+ var attributes = node === null || node === void 0 ? void 0 : (_node$openingElement = node.openingElement) === null || _node$openingElement === void 0 ? void 0 : _node$openingElement.attributes;
9268
+ if (!attributes) return optionCount;
9269
+
9270
+ // Count options from props passed to the component
9271
+ var optionsAttribute = ramda.find(neetoCist.matches(OPTIONS_ARRAY_PATTERN), attributes);
9272
+ var expression = optionsAttribute === null || optionsAttribute === void 0 ? void 0 : (_optionsAttribute$val = optionsAttribute.value) === null || _optionsAttribute$val === void 0 ? void 0 : _optionsAttribute$val.expression;
9273
+ if (expression) {
9274
+ var elementsCount = countArrayElements(expression, context);
9275
+ optionCount += elementsCount;
9276
+ }
9277
+
9278
+ // Count options from spread props eg: <Component {...{options}} />
9279
+ var spreadAttributes = attributes.filter(function (attribute) {
9280
+ return neetoCist.matches(SPREAD_ATTRIBUTE_PATTERN, attribute);
9281
+ });
9282
+ spreadAttributes.forEach(function (attribute) {
9283
+ if (!attribute.argument) return;
9284
+ if (!(attribute.argument.type === TYPES.OBJECT_EXPRESSION)) return;
9285
+ var optionsProperty = ramda.find(neetoCist.matches(SPREAD_OPTION_PROPERTY_PATTERN), attribute.argument.properties);
9286
+ var optionsPropertyValue = optionsProperty === null || optionsProperty === void 0 ? void 0 : optionsProperty.value;
9287
+ if (!optionsPropertyValue) return;
9288
+ var elementsCount = countArrayElements(optionsPropertyValue, context);
9289
+ optionCount += elementsCount;
9290
+ });
9291
+ return optionCount;
9292
+ };
9293
+ var preferRadioButtonsForFewOptions = {
9294
+ meta: {
9295
+ type: "warn",
9296
+ docs: {
9297
+ url: "https://github.com/bigbinary/eslint-plugin-neeto/blob/main/docs/src/rules/prefer-radio-buttons-for-few-options.md",
9298
+ description: PREFER_RADIO_BUTTONS_FOR_FEW_OPTIONS
9299
+ }
9300
+ },
9301
+ create: function create(context) {
9302
+ return {
9303
+ JSXElement: function JSXElement(node) {
9304
+ if (!neetoCist.matches(DROPDOWN_COMPONENT_PATTERN, node)) return;
9305
+ var optionCount = countOptions(node, context);
9306
+ if (optionCount > 0 && optionCount < MIN_OPTIONS_THRESHOLD) {
9307
+ context.report({
9308
+ node: node,
9309
+ message: PREFER_RADIO_BUTTONS_FOR_FEW_OPTIONS
9310
+ });
9311
+ }
9312
+ }
9313
+ };
9314
+ }
9315
+ };
9316
+
9162
9317
  var NEETO_PACKAGES = ["@bigbinary/neetoui", "@bigbinary/neeto-icons"];
9163
9318
 
9164
9319
  var importedFromNeeto = function importedFromNeeto(context, node) {
@@ -10592,26 +10747,37 @@
10592
10747
  return HELP_URL_CONSTANT_PATTERN.test(_name2);
10593
10748
  }
10594
10749
  };
10750
+ var VALID_HELP_URL_BASE_PATTERN = {
10751
+ type: TYPES.LITERAL,
10752
+ value: function value(_value) {
10753
+ return typeof _value === "string" && HELP_URL_BASE_REGEX.test(_value);
10754
+ }
10755
+ };
10595
10756
  var HELP_URLS_FILENAME = "helpUrls.js";
10596
10757
  var HELP_URL_CONSTANT_PATTERN = /^HELP_.*_URL$/;
10597
10758
  var HELP_DOCS_BASE_URL_CONSTANT_NAME = "HELP_DOCS_BASE_URL";
10598
10759
  var ARTICLE_LINK_REGEX = /\/articles\//;
10599
10760
  // Regex matches help.neeto<subdomain>.com
10600
- var HELP_URL_BASE_REGEX = /help\.neeto[a-z]+\.com/;
10761
+ var HELP_URL_BASE_REGEX = /help\.(neeto[a-z]+)\.com/;
10601
10762
  // Regex for the old/incorrect format: neeto<product>help.neetokb.com
10602
10763
  var OLD_HELP_URL_BASE_REGEX = /neeto[a-z]+help\.neetokb\.com/;
10764
+ // Regex matches HELP_NEETO..._BASE_URL
10765
+ var HELP_URL_OTHER_NEETO_PRODUCT_CONSTANT_REGEX = /^HELP_NEETO.*_BASE_URL$/;
10766
+ var HELP_URL_OTHER_NEETO_PRODUCT_CONSTANT_NAME_TEMPLATE = function HELP_URL_OTHER_NEETO_PRODUCT_CONSTANT_NAME_TEMPLATE(neetoProductName) {
10767
+ return "HELP_".concat(neetoProductName, "_BASE_URL");
10768
+ };
10603
10769
  var BASE_URL_DECLARATOR_PATTERN = {
10604
10770
  id: {
10605
10771
  name: HELP_DOCS_BASE_URL_CONSTANT_NAME
10606
10772
  },
10607
10773
  init: {
10608
10774
  type: TYPES.LITERAL,
10609
- value: function value(_value) {
10610
- return typeof _value === "string";
10775
+ value: function value(_value2) {
10776
+ return typeof _value2 === "string";
10611
10777
  }
10612
10778
  }
10613
10779
  };
10614
- var VALID_HELP_URL_INIT_PATTERN = matchesAny([VALID_TEMPLATE_LITERAL_INIT_PATTERN, VALID_IDENTIFIER_INIT_PATTERN]);
10780
+ var VALID_HELP_URL_INIT_PATTERN = matchesAny([VALID_TEMPLATE_LITERAL_INIT_PATTERN, VALID_IDENTIFIER_INIT_PATTERN, VALID_HELP_URL_BASE_PATTERN]);
10615
10781
  var DANGLING_HELP_URL_DECLARATOR_PATTERN = {
10616
10782
  type: TYPES.VARIABLE_DECLARATOR,
10617
10783
  id: {
@@ -10625,6 +10791,19 @@
10625
10791
  }
10626
10792
  };
10627
10793
 
10794
+ var getNeetoProductNameFromHelpUrl = function getNeetoProductNameFromHelpUrl(helpUrl) {
10795
+ var match = helpUrl.match(HELP_URL_BASE_REGEX);
10796
+ return match ? match[1].toUpperCase() : null;
10797
+ };
10798
+ var getSuggestedHelpUrlOtherNeetoProductConstantName = function getSuggestedHelpUrlOtherNeetoProductConstantName(helpUrl) {
10799
+ var neetoProductName = getNeetoProductNameFromHelpUrl(helpUrl);
10800
+ return HELP_URL_OTHER_NEETO_PRODUCT_CONSTANT_NAME_TEMPLATE(neetoProductName);
10801
+ };
10802
+ var checkIfConstantNameMatchesHelpUrl = function checkIfConstantNameMatchesHelpUrl(constantName, helpUrl) {
10803
+ var neetoProductName = getNeetoProductNameFromHelpUrl(helpUrl);
10804
+ return constantName === HELP_URL_OTHER_NEETO_PRODUCT_CONSTANT_NAME_TEMPLATE(neetoProductName);
10805
+ };
10806
+
10628
10807
  function _createForOfIteratorHelper$1(o, allowArrayLike) { var it = typeof Symbol !== "undefined" && o[Symbol.iterator] || o["@@iterator"]; if (!it) { if (Array.isArray(o) || (it = _unsupportedIterableToArray$1(o)) || allowArrayLike) { if (it) o = it; var i = 0; var F = function F() {}; return { s: F, n: function n() { if (i >= o.length) return { done: true }; return { done: false, value: o[i++] }; }, e: function e(_e) { throw _e; }, f: F }; } throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } var normalCompletion = true, didErr = false, err; return { s: function s() { it = it.call(o); }, n: function n() { var step = it.next(); normalCompletion = step.done; return step; }, e: function e(_e2) { didErr = true; err = _e2; }, f: function f() { try { if (!normalCompletion && it["return"] != null) it["return"](); } finally { if (didErr) throw err; } } }; }
10629
10808
  function _unsupportedIterableToArray$1(o, minLen) { if (!o) return; if (typeof o === "string") return _arrayLikeToArray$1(o, minLen); var n = Object.prototype.toString.call(o).slice(8, -1); if (n === "Object" && o.constructor) n = o.constructor.name; if (n === "Map" || n === "Set") return Array.from(o); if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray$1(o, minLen); }
10630
10809
  function _arrayLikeToArray$1(arr, len) { if (len == null || len > arr.length) len = arr.length; for (var i = 0, arr2 = new Array(len); i < len; i++) arr2[i] = arr[i]; return arr2; }
@@ -10661,9 +10840,68 @@
10661
10840
  });
10662
10841
  return false;
10663
10842
  }
10843
+ if (!neetoCist.matches(VALID_HELP_URL_BASE_PATTERN, declarator.init)) {
10844
+ context.report({
10845
+ node: declarator,
10846
+ message: baseUrlShouldBeInStandardFormat(HELP_URLS_FILENAME)
10847
+ });
10848
+ return false;
10849
+ }
10664
10850
  return true;
10665
10851
  };
10666
10852
 
10853
+ /**
10854
+ * Validates a base help URL constant declaration for other neeto products inside helpUrls.js.
10855
+ * e.g., `export const HELP_NEETOCHAT_BASE_URL = "https://help.neetochat.com";`
10856
+ */
10857
+ var validateOtherNeetoProductConstant = function validateOtherNeetoProductConstant(context, declarator) {
10858
+ var constantName = declarator.id.name;
10859
+ var init = declarator.init;
10860
+
10861
+ // Check 1: Checks that the constant name follows the naming convention (HELP_NEETO..._BASE_URL)
10862
+ if (!HELP_URL_OTHER_NEETO_PRODUCT_CONSTANT_REGEX.test(constantName)) {
10863
+ var suggestedConstantName = getSuggestedHelpUrlOtherNeetoProductConstantName(init.value);
10864
+ context.report({
10865
+ node: declarator,
10866
+ message: helpUrlOtherNeetoProductConstantNamingConvention({
10867
+ filename: HELP_URLS_FILENAME,
10868
+ constantName: constantName,
10869
+ suggestedConstantName: suggestedConstantName
10870
+ }),
10871
+ fix: function fix(fixer) {
10872
+ return fixer.replaceText(declarator.id, suggestedConstantName);
10873
+ }
10874
+ });
10875
+ return;
10876
+ }
10877
+
10878
+ //Check 2: If the constant name matches the help URL, check that the help URL is in the correct format.
10879
+ if (OLD_HELP_URL_BASE_REGEX.test(init.value)) {
10880
+ context.report({
10881
+ node: declarator,
10882
+ message: HELP_URL_INCORRECT_FORMAT
10883
+ });
10884
+ return;
10885
+ }
10886
+
10887
+ // Check 3: Checks that the constant name matches the help URL
10888
+ if (!checkIfConstantNameMatchesHelpUrl(constantName, init.value)) {
10889
+ var _suggestedConstantName = getSuggestedHelpUrlOtherNeetoProductConstantName(init.value);
10890
+ context.report({
10891
+ node: declarator,
10892
+ message: helpUrlOtherNeetoProductConstantShouldMatchHelpUrl({
10893
+ filename: HELP_URLS_FILENAME,
10894
+ constantName: constantName,
10895
+ suggestedConstantName: _suggestedConstantName
10896
+ }),
10897
+ fix: function fix(fixer) {
10898
+ return fixer.replaceText(declarator.id, _suggestedConstantName);
10899
+ }
10900
+ });
10901
+ return;
10902
+ }
10903
+ };
10904
+
10667
10905
  /**
10668
10906
  * Validates a single help URL constant declaration inside helpUrls.js.
10669
10907
  * e.g., `export const HELP_API_KEYS_URL = \`\${HELP_DOCS_BASE_URL}/p/a-d7e4285a\`;`
@@ -10681,7 +10919,7 @@
10681
10919
  }
10682
10920
  var init = declarator.init;
10683
10921
 
10684
- // Check 2: Must be a valid template literal or identifier assignment.
10922
+ // Check 2: Must be a valid template literal, identifier assignment or help URL base.
10685
10923
  if (!neetoCist.matches(VALID_HELP_URL_INIT_PATTERN, init)) {
10686
10924
  context.report({
10687
10925
  node: declarator,
@@ -10698,8 +10936,14 @@
10698
10936
  node: declarator,
10699
10937
  message: HELP_URL_SHOULD_USE_PERMALINK
10700
10938
  });
10939
+ return;
10701
10940
  }
10702
10941
  }
10942
+
10943
+ //Check 4: If it's a help URL base for other neeto products, validate the constant name.
10944
+ if (init.type === TYPES.LITERAL) {
10945
+ validateOtherNeetoProductConstant(context, declarator);
10946
+ }
10703
10947
  };
10704
10948
 
10705
10949
  /**
@@ -10807,7 +11051,7 @@
10807
11051
  url: "https://github.com/bigbinary/eslint-plugin-neeto/blob/main/docs/src/rules/standardize-help-urls.md",
10808
11052
  description: "Ensure help URLs are standardized and defined in helpUrls.js"
10809
11053
  },
10810
- fixable: null
11054
+ fixable: "code"
10811
11055
  },
10812
11056
  create: function create(context) {
10813
11057
  var filename = context.getFilename();
@@ -13205,6 +13449,7 @@
13205
13449
 
13206
13450
  var index = {
13207
13451
  rules: {
13452
+ "prefer-radio-buttons-for-few-options": preferRadioButtonsForFewOptions,
13208
13453
  "switch-children-must-be-route-or-redirect": switchChildrenMustBeRouteOrRedirect,
13209
13454
  "use-dayjs-from-neeto-commons-fronted": useDayjsFromNeetoCommonsFronted,
13210
13455
  "use-neetoui-action-block-in-formik-forms": useNeetouiActionBlockInFormikForms,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bigbinary/eslint-plugin-neeto",
3
- "version": "1.7.5",
3
+ "version": "1.8.0",
4
4
  "main": "dist/index.js",
5
5
  "license": "UNLICENSED",
6
6
  "scripts": {