最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • ESLint 之解析包名

    正文概述 掘金(小被子)   2021-01-31   767

    ESLint 配置文件中 extends 的写法多种多样,那么 ESLint 是怎么根据不同的写法找到正确的包呢?

    extends 分类

    假设有这样一个 .eslintrc.js

    module.exports = {
      extends: [
        'eslint:recommended',
        'eslint-config-react',
        'react',
        './ireneRule.js',
        'prettier/@typescript-eslint',
        'plugin:@typescript-eslint/recommended',
        'plugin:@typescript-eslint/eslint-plugin/recommended',
        'plugin:@typescript-eslint/test/recommended', // 为了测试,杜撰的一个包
        'plugin:@typescript-eslint/eslint-plugin-irene/recommended', // 为了测试,杜撰的一个包
        'plugin:prettier/recommended',
      ]
    }
    

    extends 中有三类写法:

    eslint: 开头,加载 ESLint 内置规则
    • 如果是 eslint:recommended,加载 ESLint 推荐的规则;
    • 如果是 eslint:all,加载 ESLint 所有的规则;
    plugin: 开头
    • 首先分离出 pluginName,它就是 plugin: 和最后一个 / 的之间部分;有如下几种情况:

      • plugin:@typescript-eslint/recommended 的 pluginName 是 @typescript-eslint
      • plugin:@typescript-eslint/eslint-plugin/recommended 的 pluginName 是 @typescript-eslint/eslint-plugin
      • plugin:@typescript-eslint/test/recommended 的 pluginName 是 @typescript-eslint/test
      • plugin:@typescript-eslint/eslint-plugin-irene/recommended 的 pluginName 是 @typescript-eslint/eslint-plugin-irene
      • plugin:prettier/recommended 的 pluginName 是 prettier
    • 然后根据 pluginName 得到标准化的包名;

      • 如果 pluginName 以 @ 开头,说明使用的是 scoped modules;有如下几种情况:
        • pluginName 是 @scopeName 或 @scopeName/eslint-plugin,对应的包名是 @scopeName/eslint-plugin;
          • @typescript-eslint 对应的是 @typescript-eslint/eslint-plugin
          • @typescript-eslint/eslint-plugin 对应的是 @typescript-eslint/eslint-plugin
        • pluginName 是 @scopeName/xxx,且 xxx 不以 eslint-plugin 开头,对应的包名是 @scopeName/eslint-plugin-xxx;
          • @typescript-eslint/test 对应的是 @typescript-eslint/eslint-plugin-test
        • pluginName 是 @scopeName/eslint-plugin-xxx,对应的包名是 @scopeName/eslint-plugin-xxx;
          • @typescript-eslint/eslint-plugin-irene 对应的是 @typescript-eslint/eslint-plugin-irene
      • 如果 pluginName 不以 eslint-plugin- 开头,对应的包名是 eslint-plugin-xxx;例如:prettier 对应的是 eslint-plugin-prettier
    其他
    • 一个本地路径,指向本地的 ESLint 配置,例如:./ireneRule.js
    • 以 . 开头,这是为了兼容之前的版本,不过多解释;
    • 根据 extendName 得到标准化的包名,这一步与 plugin 相同;
      • 如果 extendName 以 @ 开头,说明使用的是 scoped modules;有如下几种情况:
        • extendName 是 @scopeName 或 @scopeName/eslint-config,对应的包名是 @scopeName/eslint-config;
        • extendName 是 @scopeName/xxx,且 xxx 不以 eslint-config 开头,对应的包名是 @scopeName/eslint-config-xxx;
        • extendName 是 @scopeName/eslint-config-xxx,对应的包名是 @scopeName/eslint-config-xxx;
      • 如果 extendName 不以 eslint-config- 开头,对应的包名是 eslint-config-xxx;例如:react 对应的是 eslint-config-react

    ESLint 源码

    解析 extends 得到包名主要涉及的源码如下:

    eslint/lib/cli-engine/config-array-factory.js

    /**
     * Load configs of an element in `extends`.
     * @param {string} extendName The name of a base config.
     * @param {ConfigArrayFactoryLoadingContext} ctx The loading context.
     * @returns {IterableIterator<ConfigArrayElement>} The normalized config.
     * @private
     */
    _loadExtends(extendName, ctx) {
      debug("Loading {extends:%j} relative to %s", extendName, ctx.filePath);
      // console.log(extendName, ctx)
      try {
          if (extendName.startsWith("eslint:")) {
              return this._loadExtendedBuiltInConfig(extendName, ctx);
          }
          if (extendName.startsWith("plugin:")) {
              return this._loadExtendedPluginConfig(extendName, ctx);
          }
          return this._loadExtendedShareableConfig(extendName, ctx);
      } catch (error) {
          error.message += `\nReferenced from: ${ctx.filePath || ctx.name}`;
          throw error;
      }
    }
    
    /**
     * Load configs of an element in `extends`.
     * @param {string} extendName The name of a base config.
     * @param {ConfigArrayFactoryLoadingContext} ctx The loading context.
     * @returns {IterableIterator<ConfigArrayElement>} The normalized config.
     * @private
     */
    _loadExtendedBuiltInConfig(extendName, ctx) {
        if (extendName === "eslint:recommended") {
            return this._loadConfigData({
                ...ctx,
                filePath: eslintRecommendedPath,
                name: `${ctx.name} » ${extendName}`
            });
        }
        if (extendName === "eslint:all") {
            return this._loadConfigData({
                ...ctx,
                filePath: eslintAllPath,
                name: `${ctx.name} » ${extendName}`
            });
        }
    
        throw configMissingError(extendName, ctx.name);
    }
    
    /**
     * Load configs of an element in `extends`.
     * @param {string} extendName The name of a base config.
     * @param {ConfigArrayFactoryLoadingContext} ctx The loading context.
     * @returns {IterableIterator<ConfigArrayElement>} The normalized config.
     * @private
     */
    _loadExtendedPluginConfig(extendName, ctx) {
        const slashIndex = extendName.lastIndexOf("/");
        const pluginName = extendName.slice("plugin:".length, slashIndex);
        const configName = extendName.slice(slashIndex + 1);
    
        if (isFilePath(pluginName)) {
            throw new Error("'extends' cannot use a file path for plugins.");
        }
    
        const plugin = this._loadPlugin(pluginName, ctx);
        const configData =
            plugin.definition &&
            plugin.definition.configs[configName];
    
        if (configData) {
            return this._normalizeConfigData(configData, {
                ...ctx,
                filePath: plugin.filePath || ctx.filePath,
                name: `${ctx.name} » plugin:${plugin.id}/${configName}`
            });
        }
    
        throw plugin.error || configMissingError(extendName, ctx.filePath);
    }
    
    /**
     * Load configs of an element in `extends`.
     * @param {string} extendName The name of a base config.
     * @param {ConfigArrayFactoryLoadingContext} ctx The loading context.
     * @returns {IterableIterator<ConfigArrayElement>} The normalized config.
     * @private
     */
    _loadExtendedShareableConfig(extendName, ctx) {
        const { cwd } = internalSlotsMap.get(this);
        const relativeTo = ctx.filePath || path.join(cwd, "__placeholder__.js");
        let request;
    
        if (isFilePath(extendName)) {
            request = extendName;
        } else if (extendName.startsWith(".")) {
            request = `./${extendName}`; // For backward compatibility. A ton of tests depended on this behavior.
        } else {
            request = naming.normalizePackageName(
                extendName,
                "eslint-config"
            );
        }
    
        let filePath;
    
        try {
            filePath = ModuleResolver.resolve(request, relativeTo);
        } catch (error) {
            /* istanbul ignore else */
            if (error && error.code === "MODULE_NOT_FOUND") {
                throw configMissingError(extendName, ctx.filePath);
            }
            throw error;
        }
    
        writeDebugLogForLoading(request, relativeTo, filePath);
        return this._loadConfigData({
            ...ctx,
            filePath,
            name: `${ctx.name} » ${request}`
        });
    }
    
    /**
     * Load a given plugin.
     * @param {string} name The plugin name to load.
     * @param {ConfigArrayFactoryLoadingContext} ctx The loading context.
     * @returns {DependentPlugin} The loaded plugin.
     * @private
     */
    _loadPlugin(name, ctx) {
      debug("Loading plugin %j from %s", name, ctx.filePath);
    
      const { additionalPluginPool } = internalSlotsMap.get(this);
      const request = naming.normalizePackageName(name, "eslint-plugin");
      // console.log(name, request)
      const id = naming.getShorthandName(request, "eslint-plugin");
      const relativeTo = path.join(ctx.pluginBasePath, "__placeholder__.js");
    
      if (name.match(/\s+/u)) {
          const error = Object.assign(
              new Error(`Whitespace found in plugin name '${name}'`),
              {
                  messageTemplate: "whitespace-found",
                  messageData: { pluginName: request }
              }
          );
    
          return new ConfigDependency({
              error,
              id,
              importerName: ctx.name,
              importerPath: ctx.filePath
          });
      }
    
      // Check for additional pool.
      const plugin =
          additionalPluginPool.get(request) ||
          additionalPluginPool.get(id);
    
      if (plugin) {
          return new ConfigDependency({
              definition: normalizePlugin(plugin),
              filePath: "", // It's unknown where the plugin came from.
              id,
              importerName: ctx.name,
              importerPath: ctx.filePath
          });
      }
    
      let filePath;
      let error;
    
      try {
          filePath = ModuleResolver.resolve(request, relativeTo);
      } catch (resolveError) {
          error = resolveError;
          /* istanbul ignore else */
          if (error && error.code === "MODULE_NOT_FOUND") {
              error.messageTemplate = "plugin-missing";
              error.messageData = {
                  pluginName: request,
                  resolvePluginsRelativeTo: ctx.pluginBasePath,
                  importerName: ctx.name
              };
          }
      }
    
      if (filePath) {
          try {
              writeDebugLogForLoading(request, relativeTo, filePath);
    
              const startTime = Date.now();
              const pluginDefinition = require(filePath);
    
              debug(`Plugin ${filePath} loaded in: ${Date.now() - startTime}ms`);
    
              return new ConfigDependency({
                  definition: normalizePlugin(pluginDefinition),
                  filePath,
                  id,
                  importerName: ctx.name,
                  importerPath: ctx.filePath
              });
          } catch (loadError) {
              error = loadError;
          }
      }
    
      debug("Failed to load plugin '%s' declared in '%s'.", name, ctx.name);
      error.message = `Failed to load plugin '${name}' declared in '${ctx.name}': ${error.message}`;
      return new ConfigDependency({
          error,
          id,
          importerName: ctx.name,
          importerPath: ctx.filePath
      });
    }
    

    eslint/lib/shared/naming.js

    /**
     * Brings package name to correct format based on prefix
     * @param {string} name The name of the package.
     * @param {string} prefix Can be either "eslint-plugin", "eslint-config" or "eslint-formatter"
     * @returns {string} Normalized name of the package
     * @private
     */
    function normalizePackageName(name, prefix) {
        let normalizedName = name;
    
        /**
         * On Windows, name can come in with Windows slashes instead of Unix slashes.
         * Normalize to Unix first to avoid errors later on.
         * https://github.com/eslint/eslint/issues/5644
         */
        if (normalizedName.includes("\\")) {
            normalizedName = normalizedName.replace(/\\/gu, "/");
        }
    
        if (normalizedName.charAt(0) === "@") {
    
            /**
             * it's a scoped package
             * package name is the prefix, or just a username
             */
            const scopedPackageShortcutRegex = new RegExp(`^(@[^/]+)(?:/(?:${prefix})?)?$`, "u"),
                scopedPackageNameRegex = new RegExp(`^${prefix}(-|$)`, "u");
    
            if (scopedPackageShortcutRegex.test(normalizedName)) {
                normalizedName = normalizedName.replace(scopedPackageShortcutRegex, `$1/${prefix}`);
                // console.log(normalizedName)
            } else if (!scopedPackageNameRegex.test(normalizedName.split("/")[1])) {
    
                /**
                 * for scoped packages, insert the prefix after the first / unless
                 * the path is already @scope/eslint or @scope/eslint-xxx-yyy
                 */
                normalizedName = normalizedName.replace(/^@([^/]+)\/(.*)$/u, `@$1/${prefix}-$2`);
            }
        } else if (!normalizedName.startsWith(`${prefix}-`)) {
            normalizedName = `${prefix}-${normalizedName}`;
        }
    
        return normalizedName;
    }
    

    如何 debug ESLint?

    • 在 node_modules 中的 ESLint 源码打上断点;

    • 项目根目录下运行如下命令,其中 -c .eslintrc.js 指定 ESLint 配置文件,./src/storage/testEslint.ts 是待校验的文件;

      node --inspect-brk ./node_modules/.bin/./eslint -c .eslintrc.js ./src/storage/testEslint.ts
      
    • 打开 chrome://inspect/#devices,点击 inspect

      ESLint 之解析包名

    • 然后就可在 Chrome 或 VSCode 中调试 ESLint 源码;

    参考

    英文:shareable-configs

    中文:shareable-configs


    起源地下载网 » ESLint 之解析包名

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元