@agilebot/eslint-plugin 0.1.1
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/LICENSE +9 -0
 - package/README.md +11 -0
 - package/lib/index.js +52 -0
 - package/lib/rules/import/enforce-icon-alias.js +43 -0
 - package/lib/rules/import/monorepo.js +44 -0
 - package/lib/rules/intl/id-missing.js +105 -0
 - package/lib/rules/intl/id-prefix.js +97 -0
 - package/lib/rules/intl/id-unused.js +117 -0
 - package/lib/rules/intl/no-default.js +57 -0
 - package/lib/rules/others/no-unnecessary-template-literals.js +38 -0
 - package/lib/rules/react/better-exhaustive-deps.js +1935 -0
 - package/lib/rules/react/hook-use-ref.js +37 -0
 - package/lib/rules/react/no-inline-styles.js +87 -0
 - package/lib/rules/react/prefer-named-property-access.js +105 -0
 - package/lib/rules/tss/class-naming.js +43 -0
 - package/lib/rules/tss/no-color-value.js +59 -0
 - package/lib/rules/tss/unused-classes.js +108 -0
 - package/lib/util/import.js +71 -0
 - package/lib/util/intl.js +127 -0
 - package/lib/util/settings.js +14 -0
 - package/lib/util/translations.js +67 -0
 - package/lib/util/tss.js +104 -0
 - package/package.json +29 -0
 
    
        package/LICENSE
    ADDED
    
    | 
         @@ -0,0 +1,9 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            The MIT License (MIT)
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            Copyright (c) 2024 Agilebot, Inc.
         
     | 
| 
      
 4 
     | 
    
         
            +
             
     | 
| 
      
 5 
     | 
    
         
            +
            Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
         
     | 
| 
      
 6 
     | 
    
         
            +
             
     | 
| 
      
 7 
     | 
    
         
            +
            The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
         
     | 
| 
      
 8 
     | 
    
         
            +
             
     | 
| 
      
 9 
     | 
    
         
            +
            THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
         
     | 
    
        package/README.md
    ADDED
    
    
    
        package/lib/index.js
    ADDED
    
    | 
         @@ -0,0 +1,52 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            const path = require('path');
         
     | 
| 
      
 2 
     | 
    
         
            +
            const { globSync } = require('fast-glob');
         
     | 
| 
      
 3 
     | 
    
         
            +
             
     | 
| 
      
 4 
     | 
    
         
            +
            // List all rules in rules folder
         
     | 
| 
      
 5 
     | 
    
         
            +
            const rulesPath = path.join(__dirname, 'rules');
         
     | 
| 
      
 6 
     | 
    
         
            +
            const ruleFiles = globSync('**/*.js', {
         
     | 
| 
      
 7 
     | 
    
         
            +
              cwd: rulesPath,
         
     | 
| 
      
 8 
     | 
    
         
            +
              depth: 2
         
     | 
| 
      
 9 
     | 
    
         
            +
            });
         
     | 
| 
      
 10 
     | 
    
         
            +
            const rules = {};
         
     | 
| 
      
 11 
     | 
    
         
            +
            ruleFiles.forEach(file => {
         
     | 
| 
      
 12 
     | 
    
         
            +
              const { dir, name } = path.parse(file);
         
     | 
| 
      
 13 
     | 
    
         
            +
              const ruleName = `${dir}/${name}`;
         
     | 
| 
      
 14 
     | 
    
         
            +
              rules[ruleName] = require(path.join(rulesPath, file));
         
     | 
| 
      
 15 
     | 
    
         
            +
            });
         
     | 
| 
      
 16 
     | 
    
         
            +
             
     | 
| 
      
 17 
     | 
    
         
            +
            module.exports.rules = rules;
         
     | 
| 
      
 18 
     | 
    
         
            +
             
     | 
| 
      
 19 
     | 
    
         
            +
            module.exports.configs = {
         
     | 
| 
      
 20 
     | 
    
         
            +
              recommended: {
         
     | 
| 
      
 21 
     | 
    
         
            +
                plugins: ['@agilebot'],
         
     | 
| 
      
 22 
     | 
    
         
            +
                rules: {
         
     | 
| 
      
 23 
     | 
    
         
            +
                  'react-hooks/exhaustive-deps': 'off',
         
     | 
| 
      
 24 
     | 
    
         
            +
                  '@agilebot/react/better-exhaustive-deps': [
         
     | 
| 
      
 25 
     | 
    
         
            +
                    'warn',
         
     | 
| 
      
 26 
     | 
    
         
            +
                    {
         
     | 
| 
      
 27 
     | 
    
         
            +
                      checkMemoizedVariableIsStatic: true,
         
     | 
| 
      
 28 
     | 
    
         
            +
                      staticHooks: {
         
     | 
| 
      
 29 
     | 
    
         
            +
                        useIpcSender: true,
         
     | 
| 
      
 30 
     | 
    
         
            +
                        useDialog: true,
         
     | 
| 
      
 31 
     | 
    
         
            +
                        useSnackbar: true,
         
     | 
| 
      
 32 
     | 
    
         
            +
                        useForm: true,
         
     | 
| 
      
 33 
     | 
    
         
            +
                        'use.*Store': {
         
     | 
| 
      
 34 
     | 
    
         
            +
                          value: true,
         
     | 
| 
      
 35 
     | 
    
         
            +
                          regexp: true
         
     | 
| 
      
 36 
     | 
    
         
            +
                        }
         
     | 
| 
      
 37 
     | 
    
         
            +
                      }
         
     | 
| 
      
 38 
     | 
    
         
            +
                    }
         
     | 
| 
      
 39 
     | 
    
         
            +
                  ],
         
     | 
| 
      
 40 
     | 
    
         
            +
                  '@agilebot/react/prefer-named-property-access': 'error',
         
     | 
| 
      
 41 
     | 
    
         
            +
                  '@agilebot/react/hook-use-ref': 'warn',
         
     | 
| 
      
 42 
     | 
    
         
            +
                  '@agilebot/react/no-inline-styles': 'error',
         
     | 
| 
      
 43 
     | 
    
         
            +
                  '@agilebot/tss/unused-classes': 'warn',
         
     | 
| 
      
 44 
     | 
    
         
            +
                  '@agilebot/tss/no-color-value': 'error',
         
     | 
| 
      
 45 
     | 
    
         
            +
                  '@agilebot/tss/class-naming': 'error',
         
     | 
| 
      
 46 
     | 
    
         
            +
                  '@agilebot/import/enforce-icon-alias': 'error',
         
     | 
| 
      
 47 
     | 
    
         
            +
                  '@agilebot/import/monorepo': 'error',
         
     | 
| 
      
 48 
     | 
    
         
            +
                  '@agilebot/others/no-unnecessary-template-literals': 'error'
         
     | 
| 
      
 49 
     | 
    
         
            +
                },
         
     | 
| 
      
 50 
     | 
    
         
            +
                settings: {}
         
     | 
| 
      
 51 
     | 
    
         
            +
              }
         
     | 
| 
      
 52 
     | 
    
         
            +
            };
         
     | 
| 
         @@ -0,0 +1,43 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            module.exports = {
         
     | 
| 
      
 2 
     | 
    
         
            +
              meta: {
         
     | 
| 
      
 3 
     | 
    
         
            +
                type: 'problem', // `problem`, `suggestion`, or `layout`
         
     | 
| 
      
 4 
     | 
    
         
            +
                docs: {
         
     | 
| 
      
 5 
     | 
    
         
            +
                  description: 'Enforce alias for @mui/icons-material imports',
         
     | 
| 
      
 6 
     | 
    
         
            +
                  recommended: true
         
     | 
| 
      
 7 
     | 
    
         
            +
                },
         
     | 
| 
      
 8 
     | 
    
         
            +
                fixable: 'code', // Or `code` or `whitespace`
         
     | 
| 
      
 9 
     | 
    
         
            +
                schema: [] // Add a schema if the rule has options
         
     | 
| 
      
 10 
     | 
    
         
            +
              },
         
     | 
| 
      
 11 
     | 
    
         
            +
             
     | 
| 
      
 12 
     | 
    
         
            +
              create(context) {
         
     | 
| 
      
 13 
     | 
    
         
            +
                return {
         
     | 
| 
      
 14 
     | 
    
         
            +
                  ImportDeclaration(node) {
         
     | 
| 
      
 15 
     | 
    
         
            +
                    if (
         
     | 
| 
      
 16 
     | 
    
         
            +
                      node.source.value !== '@mui/icons-material' &&
         
     | 
| 
      
 17 
     | 
    
         
            +
                      node.source.value !== 'mdi-material-ui'
         
     | 
| 
      
 18 
     | 
    
         
            +
                    ) {
         
     | 
| 
      
 19 
     | 
    
         
            +
                      return;
         
     | 
| 
      
 20 
     | 
    
         
            +
                    }
         
     | 
| 
      
 21 
     | 
    
         
            +
             
     | 
| 
      
 22 
     | 
    
         
            +
                    for (const specifier of node.specifiers) {
         
     | 
| 
      
 23 
     | 
    
         
            +
                      if (specifier.type !== 'ImportSpecifier') {
         
     | 
| 
      
 24 
     | 
    
         
            +
                        return;
         
     | 
| 
      
 25 
     | 
    
         
            +
                      }
         
     | 
| 
      
 26 
     | 
    
         
            +
             
     | 
| 
      
 27 
     | 
    
         
            +
                      if (specifier.imported.name === specifier.local.name) {
         
     | 
| 
      
 28 
     | 
    
         
            +
                        context.report({
         
     | 
| 
      
 29 
     | 
    
         
            +
                          node,
         
     | 
| 
      
 30 
     | 
    
         
            +
                          message: `Import for ${node.source.value} should be aliased.`,
         
     | 
| 
      
 31 
     | 
    
         
            +
                          fix: fixer => {
         
     | 
| 
      
 32 
     | 
    
         
            +
                            return fixer.replaceText(
         
     | 
| 
      
 33 
     | 
    
         
            +
                              specifier,
         
     | 
| 
      
 34 
     | 
    
         
            +
                              `${specifier.imported.name} as ${specifier.imported.name}Icon`
         
     | 
| 
      
 35 
     | 
    
         
            +
                            );
         
     | 
| 
      
 36 
     | 
    
         
            +
                          }
         
     | 
| 
      
 37 
     | 
    
         
            +
                        });
         
     | 
| 
      
 38 
     | 
    
         
            +
                      }
         
     | 
| 
      
 39 
     | 
    
         
            +
                    }
         
     | 
| 
      
 40 
     | 
    
         
            +
                  }
         
     | 
| 
      
 41 
     | 
    
         
            +
                };
         
     | 
| 
      
 42 
     | 
    
         
            +
              }
         
     | 
| 
      
 43 
     | 
    
         
            +
            };
         
     | 
| 
         @@ -0,0 +1,44 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            const { consola } = require('consola');
         
     | 
| 
      
 2 
     | 
    
         
            +
            const { getSetting } = require('../../util/settings');
         
     | 
| 
      
 3 
     | 
    
         
            +
             
     | 
| 
      
 4 
     | 
    
         
            +
            module.exports = {
         
     | 
| 
      
 5 
     | 
    
         
            +
              meta: {
         
     | 
| 
      
 6 
     | 
    
         
            +
                type: 'problem', // `problem`, `suggestion`, or `layout`
         
     | 
| 
      
 7 
     | 
    
         
            +
                docs: {
         
     | 
| 
      
 8 
     | 
    
         
            +
                  description: 'Enforce import styles for monorepo',
         
     | 
| 
      
 9 
     | 
    
         
            +
                  recommended: true
         
     | 
| 
      
 10 
     | 
    
         
            +
                },
         
     | 
| 
      
 11 
     | 
    
         
            +
                fixable: 'code', // Or `code` or `whitespace`
         
     | 
| 
      
 12 
     | 
    
         
            +
                schema: [] // Add a schema if the rule has options
         
     | 
| 
      
 13 
     | 
    
         
            +
              },
         
     | 
| 
      
 14 
     | 
    
         
            +
             
     | 
| 
      
 15 
     | 
    
         
            +
              create(context) {
         
     | 
| 
      
 16 
     | 
    
         
            +
                return {
         
     | 
| 
      
 17 
     | 
    
         
            +
                  ImportDeclaration(node) {
         
     | 
| 
      
 18 
     | 
    
         
            +
                    const prefix = getSetting(context, 'monorepo-prefix');
         
     | 
| 
      
 19 
     | 
    
         
            +
                    if (!prefix) {
         
     | 
| 
      
 20 
     | 
    
         
            +
                      consola.warn('agilebot/monorepo-prefix is not set');
         
     | 
| 
      
 21 
     | 
    
         
            +
                      return;
         
     | 
| 
      
 22 
     | 
    
         
            +
                    }
         
     | 
| 
      
 23 
     | 
    
         
            +
             
     | 
| 
      
 24 
     | 
    
         
            +
                    if (!node.source.value.startsWith(prefix)) {
         
     | 
| 
      
 25 
     | 
    
         
            +
                      return;
         
     | 
| 
      
 26 
     | 
    
         
            +
                    }
         
     | 
| 
      
 27 
     | 
    
         
            +
                    const values = node.source.value.split('/');
         
     | 
| 
      
 28 
     | 
    
         
            +
             
     | 
| 
      
 29 
     | 
    
         
            +
                    if (values[2] === 'src') {
         
     | 
| 
      
 30 
     | 
    
         
            +
                      context.report({
         
     | 
| 
      
 31 
     | 
    
         
            +
                        node,
         
     | 
| 
      
 32 
     | 
    
         
            +
                        message: `Import for ${node.source.value} should not contains src folder.`,
         
     | 
| 
      
 33 
     | 
    
         
            +
                        fix: fixer => {
         
     | 
| 
      
 34 
     | 
    
         
            +
                          const correctedPath = values
         
     | 
| 
      
 35 
     | 
    
         
            +
                            .filter((_, index) => index !== 2)
         
     | 
| 
      
 36 
     | 
    
         
            +
                            .join('/');
         
     | 
| 
      
 37 
     | 
    
         
            +
                          return fixer.replaceText(node.source, `'${correctedPath}'`);
         
     | 
| 
      
 38 
     | 
    
         
            +
                        }
         
     | 
| 
      
 39 
     | 
    
         
            +
                      });
         
     | 
| 
      
 40 
     | 
    
         
            +
                    }
         
     | 
| 
      
 41 
     | 
    
         
            +
                  }
         
     | 
| 
      
 42 
     | 
    
         
            +
                };
         
     | 
| 
      
 43 
     | 
    
         
            +
              }
         
     | 
| 
      
 44 
     | 
    
         
            +
            };
         
     | 
| 
         @@ -0,0 +1,105 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            const {
         
     | 
| 
      
 2 
     | 
    
         
            +
              sortedTemplateElements,
         
     | 
| 
      
 3 
     | 
    
         
            +
              templateLiteralDisplayStr,
         
     | 
| 
      
 4 
     | 
    
         
            +
              findFormatMessageAttrNode,
         
     | 
| 
      
 5 
     | 
    
         
            +
              findFormattedMessageAttrNode,
         
     | 
| 
      
 6 
     | 
    
         
            +
              findAttrNodeInDefineMessages,
         
     | 
| 
      
 7 
     | 
    
         
            +
              findAttrNodeInDefineMessage
         
     | 
| 
      
 8 
     | 
    
         
            +
            } = require('../../util/intl');
         
     | 
| 
      
 9 
     | 
    
         
            +
            const { getIntlIds } = require('../../util/translations');
         
     | 
| 
      
 10 
     | 
    
         
            +
             
     | 
| 
      
 11 
     | 
    
         
            +
            // ------------------------------------------------------------------------------
         
     | 
| 
      
 12 
     | 
    
         
            +
            // Rule Definition
         
     | 
| 
      
 13 
     | 
    
         
            +
            // ------------------------------------------------------------------------------
         
     | 
| 
      
 14 
     | 
    
         
            +
             
     | 
| 
      
 15 
     | 
    
         
            +
            module.exports = {
         
     | 
| 
      
 16 
     | 
    
         
            +
              meta: {
         
     | 
| 
      
 17 
     | 
    
         
            +
                docs: {
         
     | 
| 
      
 18 
     | 
    
         
            +
                  description: 'Validates intl message ids are in locale file',
         
     | 
| 
      
 19 
     | 
    
         
            +
                  category: 'Intl',
         
     | 
| 
      
 20 
     | 
    
         
            +
                  recommended: true
         
     | 
| 
      
 21 
     | 
    
         
            +
                },
         
     | 
| 
      
 22 
     | 
    
         
            +
                fixable: null,
         
     | 
| 
      
 23 
     | 
    
         
            +
                schema: []
         
     | 
| 
      
 24 
     | 
    
         
            +
              },
         
     | 
| 
      
 25 
     | 
    
         
            +
             
     | 
| 
      
 26 
     | 
    
         
            +
              create: function (context) {
         
     | 
| 
      
 27 
     | 
    
         
            +
                const translatedIds = getIntlIds(context);
         
     | 
| 
      
 28 
     | 
    
         
            +
                const translatedIdSet = new Set(translatedIds);
         
     | 
| 
      
 29 
     | 
    
         
            +
             
     | 
| 
      
 30 
     | 
    
         
            +
                // ----------------------------------------------------------------------
         
     | 
| 
      
 31 
     | 
    
         
            +
                // Helpers
         
     | 
| 
      
 32 
     | 
    
         
            +
                // ----------------------------------------------------------------------
         
     | 
| 
      
 33 
     | 
    
         
            +
             
     | 
| 
      
 34 
     | 
    
         
            +
                function isLiteralTranslated(id) {
         
     | 
| 
      
 35 
     | 
    
         
            +
                  return translatedIdSet.has(id);
         
     | 
| 
      
 36 
     | 
    
         
            +
                }
         
     | 
| 
      
 37 
     | 
    
         
            +
             
     | 
| 
      
 38 
     | 
    
         
            +
                function isTemplateTranslated(re) {
         
     | 
| 
      
 39 
     | 
    
         
            +
                  return translatedIds.some(k => re.test(k));
         
     | 
| 
      
 40 
     | 
    
         
            +
                }
         
     | 
| 
      
 41 
     | 
    
         
            +
             
     | 
| 
      
 42 
     | 
    
         
            +
                function processLiteral(node) {
         
     | 
| 
      
 43 
     | 
    
         
            +
                  if (!isLiteralTranslated(node.value)) {
         
     | 
| 
      
 44 
     | 
    
         
            +
                    context.report({
         
     | 
| 
      
 45 
     | 
    
         
            +
                      node: node,
         
     | 
| 
      
 46 
     | 
    
         
            +
                      message: 'Missing id: ' + node.value
         
     | 
| 
      
 47 
     | 
    
         
            +
                    });
         
     | 
| 
      
 48 
     | 
    
         
            +
                  }
         
     | 
| 
      
 49 
     | 
    
         
            +
                }
         
     | 
| 
      
 50 
     | 
    
         
            +
             
     | 
| 
      
 51 
     | 
    
         
            +
                function processTemplateLiteral(node) {
         
     | 
| 
      
 52 
     | 
    
         
            +
                  const exStr = sortedTemplateElements(node)
         
     | 
| 
      
 53 
     | 
    
         
            +
                    .map(e => (!e.value ? '.*' : e.value.raw))
         
     | 
| 
      
 54 
     | 
    
         
            +
                    .join('');
         
     | 
| 
      
 55 
     | 
    
         
            +
                  const re = new RegExp(exStr);
         
     | 
| 
      
 56 
     | 
    
         
            +
             
     | 
| 
      
 57 
     | 
    
         
            +
                  if (!isTemplateTranslated(re)) {
         
     | 
| 
      
 58 
     | 
    
         
            +
                    context.report({
         
     | 
| 
      
 59 
     | 
    
         
            +
                      node: node,
         
     | 
| 
      
 60 
     | 
    
         
            +
                      message: 'Missing id pattern: ' + templateLiteralDisplayStr(node)
         
     | 
| 
      
 61 
     | 
    
         
            +
                    });
         
     | 
| 
      
 62 
     | 
    
         
            +
                  }
         
     | 
| 
      
 63 
     | 
    
         
            +
                }
         
     | 
| 
      
 64 
     | 
    
         
            +
             
     | 
| 
      
 65 
     | 
    
         
            +
                function processAttrNode(node) {
         
     | 
| 
      
 66 
     | 
    
         
            +
                  if (node.value.type === 'Literal') {
         
     | 
| 
      
 67 
     | 
    
         
            +
                    return processLiteral(node.value);
         
     | 
| 
      
 68 
     | 
    
         
            +
                  }
         
     | 
| 
      
 69 
     | 
    
         
            +
                  if (
         
     | 
| 
      
 70 
     | 
    
         
            +
                    node.value.type === 'JSXExpressionContainer' &&
         
     | 
| 
      
 71 
     | 
    
         
            +
                    node.value.expression.type === 'TemplateLiteral'
         
     | 
| 
      
 72 
     | 
    
         
            +
                  ) {
         
     | 
| 
      
 73 
     | 
    
         
            +
                    return processTemplateLiteral(node.value.expression);
         
     | 
| 
      
 74 
     | 
    
         
            +
                  }
         
     | 
| 
      
 75 
     | 
    
         
            +
                  if (node.value.type === 'TemplateLiteral') {
         
     | 
| 
      
 76 
     | 
    
         
            +
                    return processTemplateLiteral(node.value);
         
     | 
| 
      
 77 
     | 
    
         
            +
                  }
         
     | 
| 
      
 78 
     | 
    
         
            +
                  context.report({
         
     | 
| 
      
 79 
     | 
    
         
            +
                    node: node,
         
     | 
| 
      
 80 
     | 
    
         
            +
                    message: 'Do not invoke intl by ' + node.value.type
         
     | 
| 
      
 81 
     | 
    
         
            +
                  });
         
     | 
| 
      
 82 
     | 
    
         
            +
                }
         
     | 
| 
      
 83 
     | 
    
         
            +
             
     | 
| 
      
 84 
     | 
    
         
            +
                // ----------------------------------------------------------------------
         
     | 
| 
      
 85 
     | 
    
         
            +
                // Public
         
     | 
| 
      
 86 
     | 
    
         
            +
                // ----------------------------------------------------------------------
         
     | 
| 
      
 87 
     | 
    
         
            +
             
     | 
| 
      
 88 
     | 
    
         
            +
                return {
         
     | 
| 
      
 89 
     | 
    
         
            +
                  JSXIdentifier: function (node) {
         
     | 
| 
      
 90 
     | 
    
         
            +
                    const attrNode = findFormattedMessageAttrNode(node, 'id');
         
     | 
| 
      
 91 
     | 
    
         
            +
                    if (attrNode) return processAttrNode(attrNode);
         
     | 
| 
      
 92 
     | 
    
         
            +
                  },
         
     | 
| 
      
 93 
     | 
    
         
            +
                  CallExpression: function (node) {
         
     | 
| 
      
 94 
     | 
    
         
            +
                    const attrNode = findFormatMessageAttrNode(node, 'id');
         
     | 
| 
      
 95 
     | 
    
         
            +
                    if (attrNode) return processAttrNode(attrNode);
         
     | 
| 
      
 96 
     | 
    
         
            +
                  },
         
     | 
| 
      
 97 
     | 
    
         
            +
                  Property: function (node) {
         
     | 
| 
      
 98 
     | 
    
         
            +
                    const attrNode =
         
     | 
| 
      
 99 
     | 
    
         
            +
                      findAttrNodeInDefineMessages(node, 'id') ||
         
     | 
| 
      
 100 
     | 
    
         
            +
                      findAttrNodeInDefineMessage(node, 'id');
         
     | 
| 
      
 101 
     | 
    
         
            +
                    if (attrNode) return processAttrNode(attrNode);
         
     | 
| 
      
 102 
     | 
    
         
            +
                  }
         
     | 
| 
      
 103 
     | 
    
         
            +
                };
         
     | 
| 
      
 104 
     | 
    
         
            +
              }
         
     | 
| 
      
 105 
     | 
    
         
            +
            };
         
     | 
| 
         @@ -0,0 +1,97 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            const {
         
     | 
| 
      
 2 
     | 
    
         
            +
              findFormatMessageAttrNode,
         
     | 
| 
      
 3 
     | 
    
         
            +
              findFormattedMessageAttrNode,
         
     | 
| 
      
 4 
     | 
    
         
            +
              findAttrNodeInDefineMessages,
         
     | 
| 
      
 5 
     | 
    
         
            +
              findAttrNodeInDefineMessage,
         
     | 
| 
      
 6 
     | 
    
         
            +
              templateLiteralDisplayStr
         
     | 
| 
      
 7 
     | 
    
         
            +
            } = require('../../util/intl');
         
     | 
| 
      
 8 
     | 
    
         
            +
             
     | 
| 
      
 9 
     | 
    
         
            +
            // ------------------------------------------------------------------------------
         
     | 
| 
      
 10 
     | 
    
         
            +
            // Rule Definition
         
     | 
| 
      
 11 
     | 
    
         
            +
            // ------------------------------------------------------------------------------
         
     | 
| 
      
 12 
     | 
    
         
            +
             
     | 
| 
      
 13 
     | 
    
         
            +
            module.exports = {
         
     | 
| 
      
 14 
     | 
    
         
            +
              meta: {
         
     | 
| 
      
 15 
     | 
    
         
            +
                docs: {
         
     | 
| 
      
 16 
     | 
    
         
            +
                  description: 'Validates intl message ids has correct prefixes',
         
     | 
| 
      
 17 
     | 
    
         
            +
                  category: 'Intl',
         
     | 
| 
      
 18 
     | 
    
         
            +
                  recommended: true
         
     | 
| 
      
 19 
     | 
    
         
            +
                },
         
     | 
| 
      
 20 
     | 
    
         
            +
                fixable: null,
         
     | 
| 
      
 21 
     | 
    
         
            +
                schema: [
         
     | 
| 
      
 22 
     | 
    
         
            +
                  {
         
     | 
| 
      
 23 
     | 
    
         
            +
                    type: 'array',
         
     | 
| 
      
 24 
     | 
    
         
            +
                    items: {
         
     | 
| 
      
 25 
     | 
    
         
            +
                      type: 'string'
         
     | 
| 
      
 26 
     | 
    
         
            +
                    }
         
     | 
| 
      
 27 
     | 
    
         
            +
                  }
         
     | 
| 
      
 28 
     | 
    
         
            +
                ]
         
     | 
| 
      
 29 
     | 
    
         
            +
              },
         
     | 
| 
      
 30 
     | 
    
         
            +
             
     | 
| 
      
 31 
     | 
    
         
            +
              create: function (context) {
         
     | 
| 
      
 32 
     | 
    
         
            +
                if (context.options[0].length === 0) {
         
     | 
| 
      
 33 
     | 
    
         
            +
                  throw new Error('Prefixes are required in settings');
         
     | 
| 
      
 34 
     | 
    
         
            +
                }
         
     | 
| 
      
 35 
     | 
    
         
            +
             
     | 
| 
      
 36 
     | 
    
         
            +
                // ----------------------------------------------------------------------
         
     | 
| 
      
 37 
     | 
    
         
            +
                // Helpers
         
     | 
| 
      
 38 
     | 
    
         
            +
                // ----------------------------------------------------------------------
         
     | 
| 
      
 39 
     | 
    
         
            +
             
     | 
| 
      
 40 
     | 
    
         
            +
                const hasPrefix = value =>
         
     | 
| 
      
 41 
     | 
    
         
            +
                  context.options[0].some(p => value.startsWith(p));
         
     | 
| 
      
 42 
     | 
    
         
            +
             
     | 
| 
      
 43 
     | 
    
         
            +
                function report(node, value) {
         
     | 
| 
      
 44 
     | 
    
         
            +
                  if (!hasPrefix(value)) {
         
     | 
| 
      
 45 
     | 
    
         
            +
                    context.report({
         
     | 
| 
      
 46 
     | 
    
         
            +
                      node: node,
         
     | 
| 
      
 47 
     | 
    
         
            +
                      message: `Invalid id prefix: ${value}`
         
     | 
| 
      
 48 
     | 
    
         
            +
                    });
         
     | 
| 
      
 49 
     | 
    
         
            +
                  }
         
     | 
| 
      
 50 
     | 
    
         
            +
                }
         
     | 
| 
      
 51 
     | 
    
         
            +
             
     | 
| 
      
 52 
     | 
    
         
            +
                function processLiteral(node) {
         
     | 
| 
      
 53 
     | 
    
         
            +
                  report(node, node.value);
         
     | 
| 
      
 54 
     | 
    
         
            +
                }
         
     | 
| 
      
 55 
     | 
    
         
            +
             
     | 
| 
      
 56 
     | 
    
         
            +
                function processTemplateLiteral(node) {
         
     | 
| 
      
 57 
     | 
    
         
            +
                  const displayStr = templateLiteralDisplayStr(node);
         
     | 
| 
      
 58 
     | 
    
         
            +
                  report(node, displayStr);
         
     | 
| 
      
 59 
     | 
    
         
            +
                }
         
     | 
| 
      
 60 
     | 
    
         
            +
             
     | 
| 
      
 61 
     | 
    
         
            +
                function processAttrNode(node) {
         
     | 
| 
      
 62 
     | 
    
         
            +
                  if (node.value.type === 'Literal') {
         
     | 
| 
      
 63 
     | 
    
         
            +
                    return processLiteral(node.value);
         
     | 
| 
      
 64 
     | 
    
         
            +
                  }
         
     | 
| 
      
 65 
     | 
    
         
            +
                  if (
         
     | 
| 
      
 66 
     | 
    
         
            +
                    node.value.type === 'JSXExpressionContainer' &&
         
     | 
| 
      
 67 
     | 
    
         
            +
                    node.value.expression.type === 'TemplateLiteral'
         
     | 
| 
      
 68 
     | 
    
         
            +
                  ) {
         
     | 
| 
      
 69 
     | 
    
         
            +
                    return processTemplateLiteral(node.value.expression);
         
     | 
| 
      
 70 
     | 
    
         
            +
                  }
         
     | 
| 
      
 71 
     | 
    
         
            +
                  if (node.value.type === 'TemplateLiteral') {
         
     | 
| 
      
 72 
     | 
    
         
            +
                    return processTemplateLiteral(node.value);
         
     | 
| 
      
 73 
     | 
    
         
            +
                  }
         
     | 
| 
      
 74 
     | 
    
         
            +
                }
         
     | 
| 
      
 75 
     | 
    
         
            +
             
     | 
| 
      
 76 
     | 
    
         
            +
                // ----------------------------------------------------------------------
         
     | 
| 
      
 77 
     | 
    
         
            +
                // Public
         
     | 
| 
      
 78 
     | 
    
         
            +
                // ----------------------------------------------------------------------
         
     | 
| 
      
 79 
     | 
    
         
            +
             
     | 
| 
      
 80 
     | 
    
         
            +
                return {
         
     | 
| 
      
 81 
     | 
    
         
            +
                  JSXIdentifier: function (node) {
         
     | 
| 
      
 82 
     | 
    
         
            +
                    const attrNode = findFormattedMessageAttrNode(node, 'id');
         
     | 
| 
      
 83 
     | 
    
         
            +
                    if (attrNode) return processAttrNode(attrNode);
         
     | 
| 
      
 84 
     | 
    
         
            +
                  },
         
     | 
| 
      
 85 
     | 
    
         
            +
                  CallExpression: function (node) {
         
     | 
| 
      
 86 
     | 
    
         
            +
                    const attrNode = findFormatMessageAttrNode(node, 'id');
         
     | 
| 
      
 87 
     | 
    
         
            +
                    if (attrNode) return processAttrNode(attrNode);
         
     | 
| 
      
 88 
     | 
    
         
            +
                  },
         
     | 
| 
      
 89 
     | 
    
         
            +
                  Property: function (node) {
         
     | 
| 
      
 90 
     | 
    
         
            +
                    const attrNode =
         
     | 
| 
      
 91 
     | 
    
         
            +
                      findAttrNodeInDefineMessages(node, 'id') ||
         
     | 
| 
      
 92 
     | 
    
         
            +
                      findAttrNodeInDefineMessage(node, 'id');
         
     | 
| 
      
 93 
     | 
    
         
            +
                    if (attrNode) return processAttrNode(attrNode);
         
     | 
| 
      
 94 
     | 
    
         
            +
                  }
         
     | 
| 
      
 95 
     | 
    
         
            +
                };
         
     | 
| 
      
 96 
     | 
    
         
            +
              }
         
     | 
| 
      
 97 
     | 
    
         
            +
            };
         
     | 
| 
         @@ -0,0 +1,117 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            const path = require('path');
         
     | 
| 
      
 2 
     | 
    
         
            +
            const fs = require('fs');
         
     | 
| 
      
 3 
     | 
    
         
            +
            const {
         
     | 
| 
      
 4 
     | 
    
         
            +
              sortedTemplateElements,
         
     | 
| 
      
 5 
     | 
    
         
            +
              findFormatMessageAttrNode,
         
     | 
| 
      
 6 
     | 
    
         
            +
              findFormattedMessageAttrNode,
         
     | 
| 
      
 7 
     | 
    
         
            +
              findAttrNodeInDefineMessages,
         
     | 
| 
      
 8 
     | 
    
         
            +
              findAttrNodeInDefineMessage
         
     | 
| 
      
 9 
     | 
    
         
            +
            } = require('../../util/intl');
         
     | 
| 
      
 10 
     | 
    
         
            +
            const { getIntlIds } = require('../../util/translations');
         
     | 
| 
      
 11 
     | 
    
         
            +
            const { getSetting } = require('../../util/settings');
         
     | 
| 
      
 12 
     | 
    
         
            +
             
     | 
| 
      
 13 
     | 
    
         
            +
            // 已经使用的id集合 key为项目目录,value为id集合Set
         
     | 
| 
      
 14 
     | 
    
         
            +
            const usedIds = new Map();
         
     | 
| 
      
 15 
     | 
    
         
            +
             
     | 
| 
      
 16 
     | 
    
         
            +
            // ------------------------------------------------------------------------------
         
     | 
| 
      
 17 
     | 
    
         
            +
            // Rule Definition
         
     | 
| 
      
 18 
     | 
    
         
            +
            // ------------------------------------------------------------------------------
         
     | 
| 
      
 19 
     | 
    
         
            +
             
     | 
| 
      
 20 
     | 
    
         
            +
            module.exports = {
         
     | 
| 
      
 21 
     | 
    
         
            +
              meta: {
         
     | 
| 
      
 22 
     | 
    
         
            +
                docs: {
         
     | 
| 
      
 23 
     | 
    
         
            +
                  description: 'Finds unused intl message ids in locale file',
         
     | 
| 
      
 24 
     | 
    
         
            +
                  category: 'Intl',
         
     | 
| 
      
 25 
     | 
    
         
            +
                  recommended: true
         
     | 
| 
      
 26 
     | 
    
         
            +
                },
         
     | 
| 
      
 27 
     | 
    
         
            +
                fixable: null,
         
     | 
| 
      
 28 
     | 
    
         
            +
                schema: []
         
     | 
| 
      
 29 
     | 
    
         
            +
              },
         
     | 
| 
      
 30 
     | 
    
         
            +
             
     | 
| 
      
 31 
     | 
    
         
            +
              create: function (context) {
         
     | 
| 
      
 32 
     | 
    
         
            +
                const projectRoot = getSetting(context, 'project-root');
         
     | 
| 
      
 33 
     | 
    
         
            +
                if (!projectRoot) {
         
     | 
| 
      
 34 
     | 
    
         
            +
                  throw new Error('projectRoot must be set in this rule');
         
     | 
| 
      
 35 
     | 
    
         
            +
                }
         
     | 
| 
      
 36 
     | 
    
         
            +
                if (!usedIds.has(projectRoot)) {
         
     | 
| 
      
 37 
     | 
    
         
            +
                  usedIds.set(projectRoot, new Set());
         
     | 
| 
      
 38 
     | 
    
         
            +
                }
         
     | 
| 
      
 39 
     | 
    
         
            +
                const usedIdSet = usedIds.get(projectRoot);
         
     | 
| 
      
 40 
     | 
    
         
            +
             
     | 
| 
      
 41 
     | 
    
         
            +
                const translatedIds = getIntlIds(context);
         
     | 
| 
      
 42 
     | 
    
         
            +
                const translatedIdSet = new Set(translatedIds);
         
     | 
| 
      
 43 
     | 
    
         
            +
             
     | 
| 
      
 44 
     | 
    
         
            +
                // ----------------------------------------------------------------------
         
     | 
| 
      
 45 
     | 
    
         
            +
                // Helpers
         
     | 
| 
      
 46 
     | 
    
         
            +
                // ----------------------------------------------------------------------
         
     | 
| 
      
 47 
     | 
    
         
            +
             
     | 
| 
      
 48 
     | 
    
         
            +
                function isLiteralTranslated(id) {
         
     | 
| 
      
 49 
     | 
    
         
            +
                  return translatedIdSet.has(id);
         
     | 
| 
      
 50 
     | 
    
         
            +
                }
         
     | 
| 
      
 51 
     | 
    
         
            +
             
     | 
| 
      
 52 
     | 
    
         
            +
                function isTemplateTranslated(re) {
         
     | 
| 
      
 53 
     | 
    
         
            +
                  return translatedIds.some(k => re.test(k));
         
     | 
| 
      
 54 
     | 
    
         
            +
                }
         
     | 
| 
      
 55 
     | 
    
         
            +
             
     | 
| 
      
 56 
     | 
    
         
            +
                function processLiteral(node) {
         
     | 
| 
      
 57 
     | 
    
         
            +
                  if (isLiteralTranslated(node.value)) {
         
     | 
| 
      
 58 
     | 
    
         
            +
                    // 将已使用的id加入usedIdSet
         
     | 
| 
      
 59 
     | 
    
         
            +
                    usedIdSet.add(node.value);
         
     | 
| 
      
 60 
     | 
    
         
            +
                  }
         
     | 
| 
      
 61 
     | 
    
         
            +
                }
         
     | 
| 
      
 62 
     | 
    
         
            +
             
     | 
| 
      
 63 
     | 
    
         
            +
                function processTemplateLiteral(node) {
         
     | 
| 
      
 64 
     | 
    
         
            +
                  const exStr = sortedTemplateElements(node)
         
     | 
| 
      
 65 
     | 
    
         
            +
                    .map(e => (!e.value ? '.*' : e.value.raw))
         
     | 
| 
      
 66 
     | 
    
         
            +
                    .join('');
         
     | 
| 
      
 67 
     | 
    
         
            +
                  const re = new RegExp(exStr);
         
     | 
| 
      
 68 
     | 
    
         
            +
             
     | 
| 
      
 69 
     | 
    
         
            +
                  if (isTemplateTranslated(re)) {
         
     | 
| 
      
 70 
     | 
    
         
            +
                    // TODO: 暂时没有这种情况
         
     | 
| 
      
 71 
     | 
    
         
            +
                  }
         
     | 
| 
      
 72 
     | 
    
         
            +
                }
         
     | 
| 
      
 73 
     | 
    
         
            +
             
     | 
| 
      
 74 
     | 
    
         
            +
                function processAttrNode(node) {
         
     | 
| 
      
 75 
     | 
    
         
            +
                  if (node.value.type === 'Literal') {
         
     | 
| 
      
 76 
     | 
    
         
            +
                    return processLiteral(node.value);
         
     | 
| 
      
 77 
     | 
    
         
            +
                  }
         
     | 
| 
      
 78 
     | 
    
         
            +
                  if (
         
     | 
| 
      
 79 
     | 
    
         
            +
                    node.value.type === 'JSXExpressionContainer' &&
         
     | 
| 
      
 80 
     | 
    
         
            +
                    node.value.expression.type === 'TemplateLiteral'
         
     | 
| 
      
 81 
     | 
    
         
            +
                  ) {
         
     | 
| 
      
 82 
     | 
    
         
            +
                    return processTemplateLiteral(node.value.expression);
         
     | 
| 
      
 83 
     | 
    
         
            +
                  }
         
     | 
| 
      
 84 
     | 
    
         
            +
                  if (node.value.type === 'TemplateLiteral') {
         
     | 
| 
      
 85 
     | 
    
         
            +
                    return processTemplateLiteral(node.value);
         
     | 
| 
      
 86 
     | 
    
         
            +
                  }
         
     | 
| 
      
 87 
     | 
    
         
            +
                }
         
     | 
| 
      
 88 
     | 
    
         
            +
             
     | 
| 
      
 89 
     | 
    
         
            +
                // ----------------------------------------------------------------------
         
     | 
| 
      
 90 
     | 
    
         
            +
                // Public
         
     | 
| 
      
 91 
     | 
    
         
            +
                // ----------------------------------------------------------------------
         
     | 
| 
      
 92 
     | 
    
         
            +
             
     | 
| 
      
 93 
     | 
    
         
            +
                return {
         
     | 
| 
      
 94 
     | 
    
         
            +
                  JSXIdentifier: function (node) {
         
     | 
| 
      
 95 
     | 
    
         
            +
                    const attrNode = findFormattedMessageAttrNode(node, 'id');
         
     | 
| 
      
 96 
     | 
    
         
            +
                    if (attrNode) return processAttrNode(attrNode);
         
     | 
| 
      
 97 
     | 
    
         
            +
                  },
         
     | 
| 
      
 98 
     | 
    
         
            +
                  CallExpression: function (node) {
         
     | 
| 
      
 99 
     | 
    
         
            +
                    const attrNode = findFormatMessageAttrNode(node, 'id');
         
     | 
| 
      
 100 
     | 
    
         
            +
                    if (attrNode) return processAttrNode(attrNode);
         
     | 
| 
      
 101 
     | 
    
         
            +
                  },
         
     | 
| 
      
 102 
     | 
    
         
            +
                  Property: function (node) {
         
     | 
| 
      
 103 
     | 
    
         
            +
                    const attrNode =
         
     | 
| 
      
 104 
     | 
    
         
            +
                      findAttrNodeInDefineMessages(node, 'id') ||
         
     | 
| 
      
 105 
     | 
    
         
            +
                      findAttrNodeInDefineMessage(node, 'id');
         
     | 
| 
      
 106 
     | 
    
         
            +
                    if (attrNode) return processAttrNode(attrNode);
         
     | 
| 
      
 107 
     | 
    
         
            +
                  },
         
     | 
| 
      
 108 
     | 
    
         
            +
                  'Program:exit': function () {
         
     | 
| 
      
 109 
     | 
    
         
            +
                    // 将usedIdSet转为数组,然后与translatedIds取差集,即为未使用的id
         
     | 
| 
      
 110 
     | 
    
         
            +
                    const unusedIds = [...translatedIdSet].filter(id => !usedIdSet.has(id));
         
     | 
| 
      
 111 
     | 
    
         
            +
                    // 在项目目录下生成intl-unused.json
         
     | 
| 
      
 112 
     | 
    
         
            +
                    const jsonPath = path.join(projectRoot, 'intl-unused.json');
         
     | 
| 
      
 113 
     | 
    
         
            +
                    fs.writeFileSync(jsonPath, JSON.stringify(unusedIds, null, 2));
         
     | 
| 
      
 114 
     | 
    
         
            +
                  }
         
     | 
| 
      
 115 
     | 
    
         
            +
                };
         
     | 
| 
      
 116 
     | 
    
         
            +
              }
         
     | 
| 
      
 117 
     | 
    
         
            +
            };
         
     | 
| 
         @@ -0,0 +1,57 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            const {
         
     | 
| 
      
 2 
     | 
    
         
            +
              findFormatMessageAttrNode,
         
     | 
| 
      
 3 
     | 
    
         
            +
              findFormattedMessageAttrNode,
         
     | 
| 
      
 4 
     | 
    
         
            +
              findAttrNodeInDefineMessages,
         
     | 
| 
      
 5 
     | 
    
         
            +
              findAttrNodeInDefineMessage
         
     | 
| 
      
 6 
     | 
    
         
            +
            } = require('../../util/intl');
         
     | 
| 
      
 7 
     | 
    
         
            +
             
     | 
| 
      
 8 
     | 
    
         
            +
            // ------------------------------------------------------------------------------
         
     | 
| 
      
 9 
     | 
    
         
            +
            // Rule Definition
         
     | 
| 
      
 10 
     | 
    
         
            +
            // ------------------------------------------------------------------------------
         
     | 
| 
      
 11 
     | 
    
         
            +
             
     | 
| 
      
 12 
     | 
    
         
            +
            module.exports = {
         
     | 
| 
      
 13 
     | 
    
         
            +
              meta: {
         
     | 
| 
      
 14 
     | 
    
         
            +
                docs: {
         
     | 
| 
      
 15 
     | 
    
         
            +
                  description: 'Validates defaultMessage is not used with react-intl',
         
     | 
| 
      
 16 
     | 
    
         
            +
                  category: 'Intl',
         
     | 
| 
      
 17 
     | 
    
         
            +
                  recommended: true
         
     | 
| 
      
 18 
     | 
    
         
            +
                },
         
     | 
| 
      
 19 
     | 
    
         
            +
                fixable: null,
         
     | 
| 
      
 20 
     | 
    
         
            +
                schema: []
         
     | 
| 
      
 21 
     | 
    
         
            +
              },
         
     | 
| 
      
 22 
     | 
    
         
            +
             
     | 
| 
      
 23 
     | 
    
         
            +
              create: function (context) {
         
     | 
| 
      
 24 
     | 
    
         
            +
                // ----------------------------------------------------------------------
         
     | 
| 
      
 25 
     | 
    
         
            +
                // Helpers
         
     | 
| 
      
 26 
     | 
    
         
            +
                // ----------------------------------------------------------------------
         
     | 
| 
      
 27 
     | 
    
         
            +
             
     | 
| 
      
 28 
     | 
    
         
            +
                function processAttrNode(node) {
         
     | 
| 
      
 29 
     | 
    
         
            +
                  context.report({
         
     | 
| 
      
 30 
     | 
    
         
            +
                    node: node,
         
     | 
| 
      
 31 
     | 
    
         
            +
                    message: 'Do not use defaultMessage'
         
     | 
| 
      
 32 
     | 
    
         
            +
                  });
         
     | 
| 
      
 33 
     | 
    
         
            +
                }
         
     | 
| 
      
 34 
     | 
    
         
            +
             
     | 
| 
      
 35 
     | 
    
         
            +
                // ----------------------------------------------------------------------
         
     | 
| 
      
 36 
     | 
    
         
            +
                // Public
         
     | 
| 
      
 37 
     | 
    
         
            +
                // ----------------------------------------------------------------------
         
     | 
| 
      
 38 
     | 
    
         
            +
             
     | 
| 
      
 39 
     | 
    
         
            +
                return {
         
     | 
| 
      
 40 
     | 
    
         
            +
                  JSXIdentifier: function (node) {
         
     | 
| 
      
 41 
     | 
    
         
            +
                    const attrNode = findFormattedMessageAttrNode(node, 'defaultMessage');
         
     | 
| 
      
 42 
     | 
    
         
            +
                    if (attrNode) return processAttrNode(attrNode);
         
     | 
| 
      
 43 
     | 
    
         
            +
                  },
         
     | 
| 
      
 44 
     | 
    
         
            +
                  CallExpression: function (node) {
         
     | 
| 
      
 45 
     | 
    
         
            +
                    const attrNode = findFormatMessageAttrNode(node, 'defaultMessage');
         
     | 
| 
      
 46 
     | 
    
         
            +
                    if (attrNode) return processAttrNode(attrNode);
         
     | 
| 
      
 47 
     | 
    
         
            +
                  },
         
     | 
| 
      
 48 
     | 
    
         
            +
                  Property: function (node) {
         
     | 
| 
      
 49 
     | 
    
         
            +
                    const attrNode =
         
     | 
| 
      
 50 
     | 
    
         
            +
                      findAttrNodeInDefineMessages(node, 'defaultMessage') ||
         
     | 
| 
      
 51 
     | 
    
         
            +
                      findAttrNodeInDefineMessage(node, 'defaultMessage');
         
     | 
| 
      
 52 
     | 
    
         
            +
             
     | 
| 
      
 53 
     | 
    
         
            +
                    if (attrNode) return processAttrNode(attrNode);
         
     | 
| 
      
 54 
     | 
    
         
            +
                  }
         
     | 
| 
      
 55 
     | 
    
         
            +
                };
         
     | 
| 
      
 56 
     | 
    
         
            +
              }
         
     | 
| 
      
 57 
     | 
    
         
            +
            };
         
     | 
| 
         @@ -0,0 +1,38 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            module.exports = {
         
     | 
| 
      
 2 
     | 
    
         
            +
              meta: {
         
     | 
| 
      
 3 
     | 
    
         
            +
                type: 'problem',
         
     | 
| 
      
 4 
     | 
    
         
            +
                docs: {
         
     | 
| 
      
 5 
     | 
    
         
            +
                  description: 'Check if a template string contains only one ${}',
         
     | 
| 
      
 6 
     | 
    
         
            +
                  recommended: true
         
     | 
| 
      
 7 
     | 
    
         
            +
                },
         
     | 
| 
      
 8 
     | 
    
         
            +
                fixable: 'code', // Or `code` or `whitespace`
         
     | 
| 
      
 9 
     | 
    
         
            +
                schema: [] // Add a schema if the rule has options
         
     | 
| 
      
 10 
     | 
    
         
            +
              },
         
     | 
| 
      
 11 
     | 
    
         
            +
             
     | 
| 
      
 12 
     | 
    
         
            +
              create(context) {
         
     | 
| 
      
 13 
     | 
    
         
            +
                return {
         
     | 
| 
      
 14 
     | 
    
         
            +
                  TemplateLiteral(node) {
         
     | 
| 
      
 15 
     | 
    
         
            +
                    // Get the entire source code of the template string
         
     | 
| 
      
 16 
     | 
    
         
            +
                    const code = context.getSourceCode().getText(node);
         
     | 
| 
      
 17 
     | 
    
         
            +
                    // Check if the template string contains only one `${}`
         
     | 
| 
      
 18 
     | 
    
         
            +
                    if (
         
     | 
| 
      
 19 
     | 
    
         
            +
                      code.startsWith('`${') &&
         
     | 
| 
      
 20 
     | 
    
         
            +
                      code.endsWith('}`') &&
         
     | 
| 
      
 21 
     | 
    
         
            +
                      code.split('${').length === 2
         
     | 
| 
      
 22 
     | 
    
         
            +
                    ) {
         
     | 
| 
      
 23 
     | 
    
         
            +
                      context.report({
         
     | 
| 
      
 24 
     | 
    
         
            +
                        node,
         
     | 
| 
      
 25 
     | 
    
         
            +
                        message: 'Unnecessary template string with only one ${}.',
         
     | 
| 
      
 26 
     | 
    
         
            +
                        fix(fixer) {
         
     | 
| 
      
 27 
     | 
    
         
            +
                          // Replace the entire template string with the content inside ${}
         
     | 
| 
      
 28 
     | 
    
         
            +
                          return fixer.replaceText(
         
     | 
| 
      
 29 
     | 
    
         
            +
                            node,
         
     | 
| 
      
 30 
     | 
    
         
            +
                            code.substring(3, code.length - 2)
         
     | 
| 
      
 31 
     | 
    
         
            +
                          );
         
     | 
| 
      
 32 
     | 
    
         
            +
                        }
         
     | 
| 
      
 33 
     | 
    
         
            +
                      });
         
     | 
| 
      
 34 
     | 
    
         
            +
                    }
         
     | 
| 
      
 35 
     | 
    
         
            +
                  }
         
     | 
| 
      
 36 
     | 
    
         
            +
                };
         
     | 
| 
      
 37 
     | 
    
         
            +
              }
         
     | 
| 
      
 38 
     | 
    
         
            +
            };
         
     |