@agilebot/eslint-plugin 0.1.2 → 0.1.4
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/lib/index.js +59 -52
- package/lib/rules/import/monorepo.js +7 -2
- package/lib/rules/intl/id-unused.js +2 -2
- package/lib/rules/react/better-exhaustive-deps.js +44 -58
- package/lib/rules/react/prefer-named-property-access.js +1 -1
- package/lib/rules/tss/class-naming.js +1 -1
- package/lib/rules/tss/no-color-value.js +0 -1
- package/lib/rules/tss/unused-classes.js +1 -1
- package/lib/util/intl.js +6 -6
- package/lib/util/translations.js +3 -4
- package/lib/util/tss.js +12 -7
- package/package.json +12 -6
- package/lib/util/import.js +0 -71
    
        package/lib/index.js
    CHANGED
    
    | @@ -1,52 +1,59 @@ | |
| 1 | 
            -
            // @ts-check
         | 
| 2 | 
            -
            const path = require('path');
         | 
| 3 | 
            -
            const { globSync } = require('fast-glob');
         | 
| 4 | 
            -
             | 
| 5 | 
            -
            // List all rules in rules folder
         | 
| 6 | 
            -
            const rulesPath = path.join(__dirname, 'rules');
         | 
| 7 | 
            -
            const ruleFiles = globSync('**/*.js', {
         | 
| 8 | 
            -
              cwd: rulesPath
         | 
| 9 | 
            -
            });
         | 
| 10 | 
            -
             | 
| 11 | 
            -
             | 
| 12 | 
            -
             | 
| 13 | 
            -
             | 
| 14 | 
            -
             | 
| 15 | 
            -
             | 
| 16 | 
            -
             | 
| 17 | 
            -
             | 
| 18 | 
            -
             | 
| 19 | 
            -
             | 
| 20 | 
            -
             | 
| 21 | 
            -
             | 
| 22 | 
            -
             | 
| 23 | 
            -
             | 
| 24 | 
            -
             | 
| 25 | 
            -
             | 
| 26 | 
            -
             | 
| 27 | 
            -
             | 
| 28 | 
            -
             | 
| 29 | 
            -
             | 
| 30 | 
            -
             | 
| 31 | 
            -
             | 
| 32 | 
            -
             | 
| 33 | 
            -
                        ' | 
| 34 | 
            -
                          value: true,
         | 
| 35 | 
            -
                          regexp: true
         | 
| 36 | 
            -
                        }
         | 
| 37 | 
            -
             | 
| 38 | 
            -
             | 
| 39 | 
            -
             | 
| 40 | 
            -
             | 
| 41 | 
            -
             | 
| 42 | 
            -
             | 
| 43 | 
            -
             | 
| 44 | 
            -
             | 
| 45 | 
            -
             | 
| 46 | 
            -
                   | 
| 47 | 
            -
                  '@agilebot/ | 
| 48 | 
            -
                  '@agilebot/ | 
| 49 | 
            -
             | 
| 50 | 
            -
             | 
| 51 | 
            -
             | 
| 52 | 
            -
             | 
| 1 | 
            +
            // @ts-check
         | 
| 2 | 
            +
            const path = require('node:path');
         | 
| 3 | 
            +
            const { globSync } = require('fast-glob');
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            // List all rules in rules folder
         | 
| 6 | 
            +
            const rulesPath = path.join(__dirname, 'rules');
         | 
| 7 | 
            +
            const ruleFiles = globSync('**/*.js', {
         | 
| 8 | 
            +
              cwd: rulesPath
         | 
| 9 | 
            +
            });
         | 
| 10 | 
            +
             | 
| 11 | 
            +
            /**
         | 
| 12 | 
            +
             * @type {import('eslint').Linter.RulesRecord}
         | 
| 13 | 
            +
             */
         | 
| 14 | 
            +
            const rules = {};
         | 
| 15 | 
            +
            ruleFiles.forEach(file => {
         | 
| 16 | 
            +
              const { dir, name } = path.parse(file);
         | 
| 17 | 
            +
              const ruleName = `${dir}/${name}`;
         | 
| 18 | 
            +
              rules[ruleName] = require(path.join(rulesPath, file));
         | 
| 19 | 
            +
            });
         | 
| 20 | 
            +
             | 
| 21 | 
            +
            module.exports.rules = rules;
         | 
| 22 | 
            +
             | 
| 23 | 
            +
            module.exports.configs = {
         | 
| 24 | 
            +
              recommended: {
         | 
| 25 | 
            +
                plugins: ['@agilebot'],
         | 
| 26 | 
            +
                rules: {
         | 
| 27 | 
            +
                  'react-hooks/exhaustive-deps': 'off',
         | 
| 28 | 
            +
                  '@agilebot/react/better-exhaustive-deps': [
         | 
| 29 | 
            +
                    'warn',
         | 
| 30 | 
            +
                    {
         | 
| 31 | 
            +
                      checkMemoizedVariableIsStatic: true,
         | 
| 32 | 
            +
                      staticHooks: {
         | 
| 33 | 
            +
                        'useIpc.*': {
         | 
| 34 | 
            +
                          value: true,
         | 
| 35 | 
            +
                          regexp: true
         | 
| 36 | 
            +
                        },
         | 
| 37 | 
            +
                        useDialog: true,
         | 
| 38 | 
            +
                        useSnackbar: true,
         | 
| 39 | 
            +
                        useForm: true,
         | 
| 40 | 
            +
                        'use.*Store': {
         | 
| 41 | 
            +
                          value: true,
         | 
| 42 | 
            +
                          regexp: true
         | 
| 43 | 
            +
                        }
         | 
| 44 | 
            +
                      }
         | 
| 45 | 
            +
                    }
         | 
| 46 | 
            +
                  ],
         | 
| 47 | 
            +
                  '@agilebot/react/prefer-named-property-access': 'error',
         | 
| 48 | 
            +
                  '@agilebot/react/hook-use-ref': 'warn',
         | 
| 49 | 
            +
                  '@agilebot/react/no-inline-styles': 'error',
         | 
| 50 | 
            +
                  '@agilebot/tss/unused-classes': 'warn',
         | 
| 51 | 
            +
                  '@agilebot/tss/no-color-value': 'error',
         | 
| 52 | 
            +
                  '@agilebot/tss/class-naming': 'error',
         | 
| 53 | 
            +
                  '@agilebot/import/enforce-icon-alias': 'error',
         | 
| 54 | 
            +
                  '@agilebot/import/monorepo': 'error',
         | 
| 55 | 
            +
                  '@agilebot/others/no-unnecessary-template-literals': 'error'
         | 
| 56 | 
            +
                },
         | 
| 57 | 
            +
                settings: {}
         | 
| 58 | 
            +
              }
         | 
| 59 | 
            +
            };
         | 
| @@ -1,6 +1,7 @@ | |
| 1 | 
            -
            const { consola } = require('consola');
         | 
| 2 1 | 
             
            const { getSetting } = require('../../util/settings');
         | 
| 3 2 |  | 
| 3 | 
            +
            let warnedForMissingPrefix = false;
         | 
| 4 | 
            +
             | 
| 4 5 | 
             
            module.exports = {
         | 
| 5 6 | 
             
              meta: {
         | 
| 6 7 | 
             
                type: 'problem', // `problem`, `suggestion`, or `layout`
         | 
| @@ -17,7 +18,11 @@ module.exports = { | |
| 17 18 | 
             
                  ImportDeclaration(node) {
         | 
| 18 19 | 
             
                    const prefix = getSetting(context, 'monorepo-prefix');
         | 
| 19 20 | 
             
                    if (!prefix) {
         | 
| 20 | 
            -
                       | 
| 21 | 
            +
                      if (!warnedForMissingPrefix) {
         | 
| 22 | 
            +
                        // eslint-disable-next-line no-console -- this is a CLI tool
         | 
| 23 | 
            +
                        console.error('Warning: agilebot/monorepo-prefix is not set.');
         | 
| 24 | 
            +
                        warnedForMissingPrefix = true;
         | 
| 25 | 
            +
                      }
         | 
| 21 26 | 
             
                      return;
         | 
| 22 27 | 
             
                    }
         | 
| 23 28 |  | 
| @@ -1,6 +1,3 @@ | |
| 1 | 
            -
            /* eslint-disable no-case-declarations */
         | 
| 2 | 
            -
            /* eslint-disable no-continue */
         | 
| 3 | 
            -
             | 
| 4 1 | 
             
            /**
         | 
| 5 2 | 
             
             * Copyright (c) Facebook, Inc. and its affiliates.
         | 
| 6 3 | 
             
             *
         | 
| @@ -96,12 +93,13 @@ module.exports = { | |
| 96 93 | 
             
                };
         | 
| 97 94 |  | 
| 98 95 | 
             
                function reportProblem(problem) {
         | 
| 99 | 
            -
                  if ( | 
| 100 | 
            -
                    // Used to enable legacy behavior. Dangerous.
         | 
| 96 | 
            +
                  if (
         | 
| 97 | 
            +
                    enableDangerousAutofixThisMayCauseInfiniteLoops && // Used to enable legacy behavior. Dangerous.
         | 
| 101 98 | 
             
                    // Keep this as an option until major IDEs upgrade (including VSCode FB ESLint extension).
         | 
| 102 | 
            -
                     | 
| 103 | 
            -
             | 
| 104 | 
            -
             | 
| 99 | 
            +
                    Array.isArray(problem.suggest) &&
         | 
| 100 | 
            +
                    problem.suggest.length > 0
         | 
| 101 | 
            +
                  ) {
         | 
| 102 | 
            +
                    problem.fix = problem.suggest[0].fix;
         | 
| 105 103 | 
             
                  }
         | 
| 106 104 | 
             
                  context.report(problem);
         | 
| 107 105 | 
             
                }
         | 
| @@ -286,17 +284,14 @@ module.exports = { | |
| 286 284 | 
             
                          if (name === 'useState') {
         | 
| 287 285 | 
             
                            const references = resolved.references;
         | 
| 288 286 | 
             
                            let writeCount = 0;
         | 
| 289 | 
            -
                            for ( | 
| 290 | 
            -
                              if ( | 
| 287 | 
            +
                            for (const reference of references) {
         | 
| 288 | 
            +
                              if (reference.isWrite()) {
         | 
| 291 289 | 
             
                                writeCount++;
         | 
| 292 290 | 
             
                              }
         | 
| 293 291 | 
             
                              if (writeCount > 1) {
         | 
| 294 292 | 
             
                                return false;
         | 
| 295 293 | 
             
                              }
         | 
| 296 | 
            -
                              setStateCallSites.set(
         | 
| 297 | 
            -
                                references[i].identifier,
         | 
| 298 | 
            -
                                id.elements[0]
         | 
| 299 | 
            -
                              );
         | 
| 294 | 
            +
                              setStateCallSites.set(reference.identifier, id.elements[0]);
         | 
| 300 295 | 
             
                            }
         | 
| 301 296 | 
             
                          }
         | 
| 302 297 | 
             
                          // Setter is stable.
         | 
| @@ -304,8 +299,8 @@ module.exports = { | |
| 304 299 | 
             
                        } else if (id.elements[0] === resolved.identifiers[0]) {
         | 
| 305 300 | 
             
                          if (name === 'useState') {
         | 
| 306 301 | 
             
                            const references = resolved.references;
         | 
| 307 | 
            -
                            for ( | 
| 308 | 
            -
                              stateVariables.add( | 
| 302 | 
            +
                            for (const reference of references) {
         | 
| 303 | 
            +
                              stateVariables.add(reference.identifier);
         | 
| 309 304 | 
             
                            }
         | 
| 310 305 | 
             
                          }
         | 
| 311 306 | 
             
                          // State variable itself is dynamic.
         | 
| @@ -317,13 +312,11 @@ module.exports = { | |
| 317 312 | 
             
                      if (
         | 
| 318 313 | 
             
                        id.type === 'ArrayPattern' &&
         | 
| 319 314 | 
             
                        id.elements.length === 2 &&
         | 
| 320 | 
            -
                        Array.isArray(resolved.identifiers)
         | 
| 315 | 
            +
                        Array.isArray(resolved.identifiers) && // Is second tuple value the same reference we're checking?
         | 
| 316 | 
            +
                        id.elements[1] === resolved.identifiers[0]
         | 
| 321 317 | 
             
                      ) {
         | 
| 322 | 
            -
                        //  | 
| 323 | 
            -
                         | 
| 324 | 
            -
                          // Setter is stable.
         | 
| 325 | 
            -
                          return true;
         | 
| 326 | 
            -
                        }
         | 
| 318 | 
            +
                        // Setter is stable.
         | 
| 319 | 
            +
                        return true;
         | 
| 327 320 | 
             
                      }
         | 
| 328 321 | 
             
                    } else if (
         | 
| 329 322 | 
             
                      options.checkMemoizedVariableIsStatic &&
         | 
| @@ -350,7 +343,7 @@ module.exports = { | |
| 350 343 | 
             
                        );
         | 
| 351 344 |  | 
| 352 345 | 
             
                        if (
         | 
| 353 | 
            -
                           | 
| 346 | 
            +
                          dependencyRefernece !== undefined &&
         | 
| 354 347 | 
             
                          memoizedIsStableKnownHookValue(dependencyRefernece.resolved)
         | 
| 355 348 | 
             
                        ) {
         | 
| 356 349 | 
             
                          continue;
         | 
| @@ -372,7 +365,6 @@ module.exports = { | |
| 372 365 | 
             
                        }
         | 
| 373 366 | 
             
                      });
         | 
| 374 367 |  | 
| 375 | 
            -
                      // eslint-disable-next-line no-lonely-if
         | 
| 376 368 | 
             
                      if (options.staticHooks[name]) {
         | 
| 377 369 | 
             
                        const staticParts = options.staticHooks[name];
         | 
| 378 370 | 
             
                        if (staticParts === true) {
         | 
| @@ -386,9 +378,7 @@ module.exports = { | |
| 386 378 | 
             
                            Array.isArray(resolved.identifiers)
         | 
| 387 379 | 
             
                          ) {
         | 
| 388 380 | 
             
                            // find index of the resolved ident in the array pattern
         | 
| 389 | 
            -
                            const idx = id.elements. | 
| 390 | 
            -
                              ident => ident === resolved.identifiers[0]
         | 
| 391 | 
            -
                            );
         | 
| 381 | 
            +
                            const idx = id.elements.indexOf(resolved.identifiers[0]);
         | 
| 392 382 | 
             
                            if (idx >= 0) {
         | 
| 393 383 | 
             
                              return staticParts[idx];
         | 
| 394 384 | 
             
                            }
         | 
| @@ -587,8 +577,7 @@ module.exports = { | |
| 587 577 | 
             
                      // Is React managing this ref or us?
         | 
| 588 578 | 
             
                      // Let's see if we can find a .current assignment.
         | 
| 589 579 | 
             
                      let foundCurrentAssignment = false;
         | 
| 590 | 
            -
                      for ( | 
| 591 | 
            -
                        const { identifier } = references[i];
         | 
| 580 | 
            +
                      for (const { identifier } of references) {
         | 
| 592 581 | 
             
                        const { parent } = identifier;
         | 
| 593 582 | 
             
                        if (
         | 
| 594 583 | 
             
                          parent != null &&
         | 
| @@ -764,8 +753,8 @@ module.exports = { | |
| 764 753 | 
             
                          declaredDependencyNode,
         | 
| 765 754 | 
             
                          null
         | 
| 766 755 | 
             
                        );
         | 
| 767 | 
            -
                      } catch ( | 
| 768 | 
            -
                        if (/Unsupported node type/.test( | 
| 756 | 
            +
                      } catch (err) {
         | 
| 757 | 
            +
                        if (/Unsupported node type/.test(err.message)) {
         | 
| 769 758 | 
             
                          if (declaredDependencyNode.type === 'Literal') {
         | 
| 770 759 | 
             
                            if (dependencies.has(declaredDependencyNode.value)) {
         | 
| 771 760 | 
             
                              reportProblem({
         | 
| @@ -795,7 +784,7 @@ module.exports = { | |
| 795 784 |  | 
| 796 785 | 
             
                          return;
         | 
| 797 786 | 
             
                        }
         | 
| 798 | 
            -
                        throw  | 
| 787 | 
            +
                        throw err;
         | 
| 799 788 | 
             
                      }
         | 
| 800 789 |  | 
| 801 790 | 
             
                      let maybeID = declaredDependencyNode;
         | 
| @@ -942,7 +931,7 @@ module.exports = { | |
| 942 931 | 
             
                      return true;
         | 
| 943 932 | 
             
                    }
         | 
| 944 933 | 
             
                    const declaredDepKeys = declaredDependencies.map(dep => dep.key);
         | 
| 945 | 
            -
                    const sortedDeclaredDepKeys = declaredDepKeys. | 
| 934 | 
            +
                    const sortedDeclaredDepKeys = [...declaredDepKeys].sort();
         | 
| 946 935 | 
             
                    return declaredDepKeys.join(',') === sortedDeclaredDepKeys.join(',');
         | 
| 947 936 | 
             
                  }
         | 
| 948 937 | 
             
                  if (areDeclaredDepsAlphabetized()) {
         | 
| @@ -978,9 +967,7 @@ module.exports = { | |
| 978 967 | 
             
                      (deps.size > 1 ? 'dependencies' : 'dependency') +
         | 
| 979 968 | 
             
                      ': ' +
         | 
| 980 969 | 
             
                      joinEnglish(
         | 
| 981 | 
            -
                         | 
| 982 | 
            -
                          .sort()
         | 
| 983 | 
            -
                          .map(name => "'" + formatDependency(name) + "'")
         | 
| 970 | 
            +
                        [...deps].sort().map(name => "'" + formatDependency(name) + "'")
         | 
| 984 971 | 
             
                      ) +
         | 
| 985 972 | 
             
                      `. Either ${fixVerb} ${
         | 
| 986 973 | 
             
                        deps.size > 1 ? 'them' : 'it'
         | 
| @@ -991,7 +978,7 @@ module.exports = { | |
| 991 978 | 
             
                  let extraWarning = '';
         | 
| 992 979 | 
             
                  if (unnecessaryDependencies.size > 0) {
         | 
| 993 980 | 
             
                    let badRef = null;
         | 
| 994 | 
            -
                     | 
| 981 | 
            +
                    [...unnecessaryDependencies.keys()].forEach(key => {
         | 
| 995 982 | 
             
                      if (badRef != null) {
         | 
| 996 983 | 
             
                        return;
         | 
| 997 984 | 
             
                      }
         | 
| @@ -1004,7 +991,7 @@ module.exports = { | |
| 1004 991 | 
             
                        ` Mutable values like '${badRef}' aren't valid dependencies ` +
         | 
| 1005 992 | 
             
                        "because mutating them doesn't re-render the component.";
         | 
| 1006 993 | 
             
                    } else if (externalDependencies.size > 0) {
         | 
| 1007 | 
            -
                      const dep =  | 
| 994 | 
            +
                      const dep = [...externalDependencies][0];
         | 
| 1008 995 | 
             
                      // Don't show this warning for things that likely just got moved *inside* the callback
         | 
| 1009 996 | 
             
                      // because in that case they're clearly not referring to globals.
         | 
| 1010 997 | 
             
                      if (!scope.set.has(dep)) {
         | 
| @@ -1028,8 +1015,7 @@ module.exports = { | |
| 1028 1015 | 
             
                      return;
         | 
| 1029 1016 | 
             
                    }
         | 
| 1030 1017 | 
             
                    let isPropsOnlyUsedInMembers = true;
         | 
| 1031 | 
            -
                    for ( | 
| 1032 | 
            -
                      const ref = refs[i];
         | 
| 1018 | 
            +
                    for (const ref of refs) {
         | 
| 1033 1019 | 
             
                      const id = fastFindReferenceWithParent(
         | 
| 1034 1020 | 
             
                        componentScope.block,
         | 
| 1035 1021 | 
             
                        ref.identifier
         | 
| @@ -1121,8 +1107,8 @@ module.exports = { | |
| 1121 1107 | 
             
                      const references = usedDep.references;
         | 
| 1122 1108 | 
             
                      let id;
         | 
| 1123 1109 | 
             
                      let maybeCall;
         | 
| 1124 | 
            -
                      for ( | 
| 1125 | 
            -
                        id =  | 
| 1110 | 
            +
                      for (const reference of references) {
         | 
| 1111 | 
            +
                        id = reference.identifier;
         | 
| 1126 1112 | 
             
                        maybeCall = id.parent;
         | 
| 1127 1113 | 
             
                        // Try to see if we have setState(someExpr(missingDep)).
         | 
| 1128 1114 | 
             
                        while (maybeCall != null && maybeCall !== componentScope.block) {
         | 
| @@ -1146,7 +1132,7 @@ module.exports = { | |
| 1146 1132 | 
             
                                  form: 'reducer'
         | 
| 1147 1133 | 
             
                                };
         | 
| 1148 1134 | 
             
                              } else {
         | 
| 1149 | 
            -
                                const resolved =  | 
| 1135 | 
            +
                                const resolved = reference.resolved;
         | 
| 1150 1136 | 
             
                                if (resolved != null) {
         | 
| 1151 1137 | 
             
                                  // If it's a parameter *and* a missing dep,
         | 
| 1152 1138 | 
             
                                  // it must be a prop or something inside a prop.
         | 
| @@ -1189,7 +1175,7 @@ module.exports = { | |
| 1189 1175 | 
             
                        case 'updater':
         | 
| 1190 1176 | 
             
                          extraWarning = ` You can also do a functional update '${
         | 
| 1191 1177 | 
             
                            setStateRecommendation.setter
         | 
| 1192 | 
            -
                          }(${setStateRecommendation.missingDep. | 
| 1178 | 
            +
                          }(${setStateRecommendation.missingDep.slice(
         | 
| 1193 1179 | 
             
                            0,
         | 
| 1194 1180 | 
             
                            1
         | 
| 1195 1181 | 
             
                          )} => ...)' if you only need '${
         | 
| @@ -1224,13 +1210,13 @@ module.exports = { | |
| 1224 1210 | 
             
                    suggest: [
         | 
| 1225 1211 | 
             
                      {
         | 
| 1226 1212 | 
             
                        desc: `Update the dependencies array to be: [${suggestedDeps
         | 
| 1227 | 
            -
                          .map(formatDependency)
         | 
| 1213 | 
            +
                          .map(element => formatDependency(element))
         | 
| 1228 1214 | 
             
                          .join(', ')}]`,
         | 
| 1229 1215 | 
             
                        fix(fixer) {
         | 
| 1230 1216 | 
             
                          // TODO: consider preserving the comments or formatting?
         | 
| 1231 1217 | 
             
                          return fixer.replaceText(
         | 
| 1232 1218 | 
             
                            declaredDependenciesNode,
         | 
| 1233 | 
            -
                            `[${suggestedDeps.map(formatDependency).join(', ')}]`
         | 
| 1219 | 
            +
                            `[${suggestedDeps.map(element => formatDependency(element)).join(', ')}]`
         | 
| 1234 1220 | 
             
                          );
         | 
| 1235 1221 | 
             
                        }
         | 
| 1236 1222 | 
             
                      }
         | 
| @@ -1520,7 +1506,7 @@ function collectRecommendations({ | |
| 1520 1506 | 
             
              declaredDependencies.forEach(({ key }) => {
         | 
| 1521 1507 | 
             
                // Does this declared dep satisfy a real need?
         | 
| 1522 1508 | 
             
                if (satisfyingDependencies.has(key)) {
         | 
| 1523 | 
            -
                  if (suggestedDependencies. | 
| 1509 | 
            +
                  if (!suggestedDependencies.includes(key)) {
         | 
| 1524 1510 | 
             
                    // Good one.
         | 
| 1525 1511 | 
             
                    suggestedDependencies.push(key);
         | 
| 1526 1512 | 
             
                  } else {
         | 
| @@ -1536,7 +1522,7 @@ function collectRecommendations({ | |
| 1536 1522 | 
             
                  // Such as resetting scroll when ID changes.
         | 
| 1537 1523 | 
             
                  // Consider them legit.
         | 
| 1538 1524 | 
             
                  // The exception is ref.current which is always wrong.
         | 
| 1539 | 
            -
                  if (suggestedDependencies. | 
| 1525 | 
            +
                  if (!suggestedDependencies.includes(key)) {
         | 
| 1540 1526 | 
             
                    suggestedDependencies.push(key);
         | 
| 1541 1527 | 
             
                  }
         | 
| 1542 1528 | 
             
                } else {
         | 
| @@ -1681,12 +1667,12 @@ function scanForConstructions({ | |
| 1681 1667 | 
             
                  while (currentScope !== scope && currentScope != null) {
         | 
| 1682 1668 | 
             
                    currentScope = currentScope.upper;
         | 
| 1683 1669 | 
             
                  }
         | 
| 1684 | 
            -
                  if ( | 
| 1685 | 
            -
                    // This reference is outside the Hook callback.
         | 
| 1670 | 
            +
                  if (
         | 
| 1671 | 
            +
                    currentScope !== scope && // This reference is outside the Hook callback.
         | 
| 1686 1672 | 
             
                    // It can only be legit if it's the deps array.
         | 
| 1687 | 
            -
                     | 
| 1688 | 
            -
             | 
| 1689 | 
            -
                     | 
| 1673 | 
            +
                    !isAncestorNodeOf(declaredDependenciesNode, reference.identifier)
         | 
| 1674 | 
            +
                  ) {
         | 
| 1675 | 
            +
                    return true;
         | 
| 1690 1676 | 
             
                  }
         | 
| 1691 1677 | 
             
                }
         | 
| 1692 1678 | 
             
                return false;
         | 
| @@ -1837,11 +1823,11 @@ function getReactiveHookCallbackIndex(calleeNode, options) { | |
| 1837 1823 | 
             
                    let name;
         | 
| 1838 1824 | 
             
                    try {
         | 
| 1839 1825 | 
             
                      name = analyzePropertyChain(node, null);
         | 
| 1840 | 
            -
                    } catch ( | 
| 1841 | 
            -
                      if (/Unsupported node type/.test( | 
| 1826 | 
            +
                    } catch (err) {
         | 
| 1827 | 
            +
                      if (/Unsupported node type/.test(err.message)) {
         | 
| 1842 1828 | 
             
                        return 0;
         | 
| 1843 1829 | 
             
                      }
         | 
| 1844 | 
            -
                      throw  | 
| 1830 | 
            +
                      throw err;
         | 
| 1845 1831 | 
             
                    }
         | 
| 1846 1832 | 
             
                    return options.additionalHooks.test(name) ? 0 : -1;
         | 
| 1847 1833 | 
             
                  }
         | 
| @@ -1863,7 +1849,7 @@ function fastFindReferenceWithParent(start, target) { | |
| 1863 1849 | 
             
              const queue = [start];
         | 
| 1864 1850 | 
             
              let item = null;
         | 
| 1865 1851 |  | 
| 1866 | 
            -
              while (queue.length) {
         | 
| 1852 | 
            +
              while (queue.length > 0) {
         | 
| 1867 1853 | 
             
                item = queue.shift();
         | 
| 1868 1854 |  | 
| 1869 1855 | 
             
                if (isSameIdentifier(item, target)) {
         | 
| @@ -1882,7 +1868,7 @@ function fastFindReferenceWithParent(start, target) { | |
| 1882 1868 | 
             
                    value.parent = item;
         | 
| 1883 1869 | 
             
                    queue.push(value);
         | 
| 1884 1870 | 
             
                  } else if (Array.isArray(value)) {
         | 
| 1885 | 
            -
                    // eslint-disable-next-line no-loop-func
         | 
| 1871 | 
            +
                    // eslint-disable-next-line no-loop-func -- FIXME
         | 
| 1886 1872 | 
             
                    value.forEach(val => {
         | 
| 1887 1873 | 
             
                      if (isNodeLike(val)) {
         | 
| 1888 1874 | 
             
                        val.parent = item;
         | 
| @@ -40,7 +40,7 @@ function* updateImportStatement(context, fixer, key) { | |
| 40 40 | 
             
              }
         | 
| 41 41 |  | 
| 42 42 | 
             
              // Named imports section is present and current key is not imported yet - add it to named imports section
         | 
| 43 | 
            -
              yield fixer.insertTextAfter(importNode.specifiers. | 
| 43 | 
            +
              yield fixer.insertTextAfter([...importNode.specifiers].pop(), `, ${key}`);
         | 
| 44 44 | 
             
            }
         | 
| 45 45 |  | 
| 46 46 | 
             
            /** @type {import('eslint').Rule.RuleModule} */
         | 
| @@ -25,7 +25,6 @@ module.exports = { | |
| 25 25 | 
             
                    function checkColorLiteral(value) {
         | 
| 26 26 | 
             
                      if (value.type === 'Literal' && typeof value.value === 'string') {
         | 
| 27 27 | 
             
                        const colorCodePattern =
         | 
| 28 | 
            -
                          // eslint-disable-next-line max-len
         | 
| 29 28 | 
             
                          /#(?:[0-9a-fA-F]{3}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})|rgb\?\(\s*(\d{1,3}\s*,\s*){2}\d{1,3}(?:\s*,\s*\d*(?:\.\d+)?)?\s*\)/g;
         | 
| 30 29 | 
             
                        const isColorCode = colorCodePattern.test(value.value);
         | 
| 31 30 | 
             
                        if (isColorCode) {
         | 
    
        package/lib/util/intl.js
    CHANGED
    
    | @@ -9,13 +9,13 @@ function findFormatMessageAttrNode(node, attrName) { | |
| 9 9 | 
             
              // Find formatMessage usages
         | 
| 10 10 | 
             
              if (
         | 
| 11 11 | 
             
                node.type === 'CallExpression' &&
         | 
| 12 | 
            -
                (node.callee.name === 'formatMessage' || node.callee.name === '$t')
         | 
| 12 | 
            +
                (node.callee.name === 'formatMessage' || node.callee.name === '$t') &&
         | 
| 13 | 
            +
                node.arguments.length > 0 &&
         | 
| 14 | 
            +
                node.arguments[0].properties
         | 
| 13 15 | 
             
              ) {
         | 
| 14 | 
            -
                 | 
| 15 | 
            -
                   | 
| 16 | 
            -
             | 
| 17 | 
            -
                  );
         | 
| 18 | 
            -
                }
         | 
| 16 | 
            +
                return node.arguments[0].properties.find(
         | 
| 17 | 
            +
                  a => a.key && a.key.name === attrName
         | 
| 18 | 
            +
                );
         | 
| 19 19 | 
             
              }
         | 
| 20 20 |  | 
| 21 21 | 
             
              // Find intl.formatMessage usages
         | 
    
        package/lib/util/translations.js
    CHANGED
    
    | @@ -1,7 +1,6 @@ | |
| 1 | 
            -
             | 
| 2 | 
            -
            const  | 
| 3 | 
            -
            const  | 
| 4 | 
            -
            const { tsImport } = require('./import');
         | 
| 1 | 
            +
            const fs = require('node:fs');
         | 
| 2 | 
            +
            const path = require('node:path');
         | 
| 3 | 
            +
            const { tsImport } = require('@agilebot/eslint-utils');
         | 
| 5 4 | 
             
            const { getSetting } = require('./settings');
         | 
| 6 5 |  | 
| 7 6 | 
             
            /**
         | 
    
        package/lib/util/tss.js
    CHANGED
    
    | @@ -12,7 +12,7 @@ function getBasicIdentifier(node) { | |
| 12 12 |  | 
| 13 13 | 
             
              if (node.type === 'TemplateLiteral') {
         | 
| 14 14 | 
             
                // Handle template literals, e.g., classes[`foo`]
         | 
| 15 | 
            -
                if (node.expressions.length) {
         | 
| 15 | 
            +
                if (node.expressions.length > 0) {
         | 
| 16 16 | 
             
                  // Template literals with expressions, e.g., classes[`foo${bar}`]
         | 
| 17 17 | 
             
                  return null;
         | 
| 18 18 | 
             
                }
         | 
| @@ -25,12 +25,17 @@ function getBasicIdentifier(node) { | |
| 25 25 |  | 
| 26 26 | 
             
            // Helper function to recursively get the base identifier from a MemberExpression node
         | 
| 27 27 | 
             
            function getBaseIdentifier(node) {
         | 
| 28 | 
            -
               | 
| 29 | 
            -
                 | 
| 30 | 
            -
             | 
| 31 | 
            -
                 | 
| 32 | 
            -
             | 
| 33 | 
            -
             | 
| 28 | 
            +
              switch (node.type) {
         | 
| 29 | 
            +
                case 'Identifier': {
         | 
| 30 | 
            +
                  return node;
         | 
| 31 | 
            +
                }
         | 
| 32 | 
            +
                case 'CallExpression': {
         | 
| 33 | 
            +
                  return getBaseIdentifier(node.callee);
         | 
| 34 | 
            +
                }
         | 
| 35 | 
            +
                case 'MemberExpression': {
         | 
| 36 | 
            +
                  return getBaseIdentifier(node.object);
         | 
| 37 | 
            +
                }
         | 
| 38 | 
            +
                // No default
         | 
| 34 39 | 
             
              }
         | 
| 35 40 | 
             
              return null;
         | 
| 36 41 | 
             
            }
         | 
    
        package/package.json
    CHANGED
    
    | @@ -1,21 +1,27 @@ | |
| 1 1 | 
             
            {
         | 
| 2 2 | 
             
              "name": "@agilebot/eslint-plugin",
         | 
| 3 | 
            -
              "version": "0.1. | 
| 3 | 
            +
              "version": "0.1.4",
         | 
| 4 4 | 
             
              "description": "Agilebot's ESLint plugin",
         | 
| 5 5 | 
             
              "main": "lib",
         | 
| 6 6 | 
             
              "license": "MIT",
         | 
| 7 | 
            +
              "keywords": [
         | 
| 8 | 
            +
                "eslint",
         | 
| 9 | 
            +
                "eslint-plugin"
         | 
| 10 | 
            +
              ],
         | 
| 7 11 | 
             
              "repository": {
         | 
| 8 12 | 
             
                "url": "sh-agilebot/frontend-toolkit",
         | 
| 9 13 | 
             
                "directory": "packages/eslint-plugin"
         | 
| 10 14 | 
             
              },
         | 
| 11 15 | 
             
              "homepage": "https://github.com/sh-agilebot/frontend-toolkit/tree/master/packages/eslint-plugin#readme",
         | 
| 16 | 
            +
              "engines": {
         | 
| 17 | 
            +
                "node": "^18.18.0 || >=20.0.0"
         | 
| 18 | 
            +
              },
         | 
| 12 19 | 
             
              "dependencies": {
         | 
| 13 20 | 
             
                "@typescript-eslint/utils": "^7.1.1",
         | 
| 14 | 
            -
                " | 
| 15 | 
            -
             | 
| 16 | 
            -
             | 
| 17 | 
            -
                " | 
| 18 | 
            -
                "sucrase": "^3.35.0"
         | 
| 21 | 
            +
                "fast-glob": "^3.3.2"
         | 
| 22 | 
            +
              },
         | 
| 23 | 
            +
              "peerDependencies": {
         | 
| 24 | 
            +
                "eslint": "^7.0.0 || ^8.0.0"
         | 
| 19 25 | 
             
              },
         | 
| 20 26 | 
             
              "files": [
         | 
| 21 27 | 
             
                "lib"
         | 
    
        package/lib/util/import.js
    DELETED
    
    | @@ -1,71 +0,0 @@ | |
| 1 | 
            -
            const path = require('path');
         | 
| 2 | 
            -
            const jiti = require('jiti');
         | 
| 3 | 
            -
            const { transform } = require('sucrase');
         | 
| 4 | 
            -
            const { getTsconfig } = require('get-tsconfig');
         | 
| 5 | 
            -
             | 
| 6 | 
            -
            /**
         | 
| 7 | 
            -
             * import ts module, like require, but support ts
         | 
| 8 | 
            -
             * @param {*} modulePath - module path
         | 
| 9 | 
            -
             */
         | 
| 10 | 
            -
            function tsImport(modulePath) {
         | 
| 11 | 
            -
              if (!modulePath) {
         | 
| 12 | 
            -
                return;
         | 
| 13 | 
            -
              }
         | 
| 14 | 
            -
              /** try to delete cache first */
         | 
| 15 | 
            -
              try {
         | 
| 16 | 
            -
                if (require.cache[modulePath]) {
         | 
| 17 | 
            -
                  delete require.cache[modulePath];
         | 
| 18 | 
            -
                }
         | 
| 19 | 
            -
              } catch (err) {
         | 
| 20 | 
            -
                /* empty */
         | 
| 21 | 
            -
              }
         | 
| 22 | 
            -
             | 
| 23 | 
            -
              try {
         | 
| 24 | 
            -
                return require(modulePath);
         | 
| 25 | 
            -
              } catch (err) {
         | 
| 26 | 
            -
                const tsconfig = getTsconfig(modulePath);
         | 
| 27 | 
            -
                const { paths, baseUrl } = tsconfig.config.compilerOptions;
         | 
| 28 | 
            -
                let basePath = path.dirname(tsconfig.path);
         | 
| 29 | 
            -
                basePath = path.resolve(basePath, baseUrl);
         | 
| 30 | 
            -
             | 
| 31 | 
            -
                const alias = resolveTsconfigPathsToAlias(paths, basePath);
         | 
| 32 | 
            -
             | 
| 33 | 
            -
                return jiti(__filename, {
         | 
| 34 | 
            -
                  interopDefault: true,
         | 
| 35 | 
            -
                  cache: false,
         | 
| 36 | 
            -
                  debug: !!process.env.DEBUG,
         | 
| 37 | 
            -
                  transform: options => {
         | 
| 38 | 
            -
                    return transform(options.source, {
         | 
| 39 | 
            -
                      transforms: ['imports', 'typescript']
         | 
| 40 | 
            -
                    });
         | 
| 41 | 
            -
                  },
         | 
| 42 | 
            -
                  alias
         | 
| 43 | 
            -
                })(modulePath);
         | 
| 44 | 
            -
              }
         | 
| 45 | 
            -
            }
         | 
| 46 | 
            -
             | 
| 47 | 
            -
            /**
         | 
| 48 | 
            -
             * Resolve tsconfig.json paths to Webpack aliases
         | 
| 49 | 
            -
             * @param  {string} paths     - tsconfig.json paths
         | 
| 50 | 
            -
             * @param  {string} basePath  - Path from tsconfig to Webpack config to create absolute aliases
         | 
| 51 | 
            -
             * @return {object}           - Webpack alias config
         | 
| 52 | 
            -
             */
         | 
| 53 | 
            -
            function resolveTsconfigPathsToAlias(paths, basePath = __dirname) {
         | 
| 54 | 
            -
              const aliases = {};
         | 
| 55 | 
            -
             | 
| 56 | 
            -
              Object.keys(paths).forEach(item => {
         | 
| 57 | 
            -
                const key = item.replace('/*', '');
         | 
| 58 | 
            -
                const value = path.resolve(
         | 
| 59 | 
            -
                  basePath,
         | 
| 60 | 
            -
                  paths[item][0].replace('/*', '').replace('*', '')
         | 
| 61 | 
            -
                );
         | 
| 62 | 
            -
             | 
| 63 | 
            -
                aliases[key] = value;
         | 
| 64 | 
            -
              });
         | 
| 65 | 
            -
             | 
| 66 | 
            -
              return aliases;
         | 
| 67 | 
            -
            }
         | 
| 68 | 
            -
             | 
| 69 | 
            -
            module.exports = {
         | 
| 70 | 
            -
              tsImport
         | 
| 71 | 
            -
            };
         |