# core/factories/eslint.scope.factory.ts

> 152 lines of code and 34 definitions.

Tree: GovLab Quality
Language: typescript
Layer: infrastructure
Canonical: https://banes-lab.com/anatomy/quality#file-quality-core-factories-eslint-scope-factory-ts
Source text: https://banes-lab.com/source/quality/core/factories/eslint.scope.factory.ts.txt

Listed in [core/factories](https://banes-lab.com/api/source/quality/core/factories.md), after [core/factories/eslint.factory.ts](https://banes-lab.com/source/quality/core/factories/eslint.factory.ts.md) and before [core/factories/eslint.setup.factory.ts](https://banes-lab.com/source/quality/core/factories/eslint.setup.factory.ts.md).

## Definitions

- `namespaceRules` (lexical_declaration, line 129)
- `manifestRulesFrom` (lexical_declaration, line 29)
- `disableTypeCheckedRules` (lexical_declaration, line 66, exported)
- `tsTestScope` (lexical_declaration, line 75, exported)
- `baseConfigs` (lexical_declaration, line 88, exported)
- `htmlScope` (lexical_declaration, line 114, exported)
- `exclusionScopes` (lexical_declaration, line 49, exported)
- `tsScopeOverride` (lexical_declaration, line 55, exported)
- `optionalScopes` (lexical_declaration, line 105, exported)
- `langScope` (lexical_declaration, line 140)
- `manifestScope` (lexical_declaration, line 37, exported)
- `relaxScope` (lexical_declaration, line 100)
- `MANIFEST_CONFIG` (lexical_declaration, line 26)
- `TYPE_CHECK_OFF_CONFIG` (lexical_declaration, line 27)
- `manifest` (lexical_declaration, line 33)
- `rules` (lexical_declaration, line 41, exported)
- `off` (lexical_declaration, line 56, exported)
- `base` (lexical_declaration, line 58, exported)
- `tsPlugin` (lexical_declaration, line 67, exported)
- `disabled` (lexical_declaration, line 71, exported)
- `{ plugins, rules, settings, tsParser, globals }` (lexical_declaration, line 89, exported)
- `language` (lexical_declaration, line 90, exported)
- `withSettings` (lexical_declaration, line 91, exported)
- `tsLanguage` (lexical_declaration, line 92, exported)
- `relaxable` (lexical_declaration, line 106, exported)
- `{ ignores }` (lexical_declaration, line 109, exported)
- `ignoreScope` (lexical_declaration, line 110, exported)
- `namespaces` (lexical_declaration, line 130)
- `out` (lexical_declaration, line 131)
- `files` (lexical_declaration, line 145)
- `parser` (lexical_declaration, line 146, exported)
- `langScopes` (lexical_declaration, line 157, exported)
- `byEcosystem` (lexical_declaration, line 158, exported)
- `scopes` (lexical_declaration, line 162, exported)

## Contained in

- [core/factories](https://banes-lab.com/anatomy/quality/folder-quality-core-factories.md)

## Uses

- [core/converters/eslint.converter.ts](https://banes-lab.com/source/quality/core/converters/eslint.converter.ts.md)
- [core/emitters/eslint.emitter.ts](https://banes-lab.com/source/quality/core/emitters/eslint.emitter.ts.md)
- [core/predicates/eslint.predicate.ts](https://banes-lab.com/source/quality/core/predicates/eslint.predicate.ts.md)

## Used by

- [core/factories/eslint.setup.factory.ts](https://banes-lab.com/source/quality/core/factories/eslint.setup.factory.ts.md)

## Linked from

- [core/converters](https://banes-lab.com/anatomy/quality/folder-quality-core-converters.md)
- [core/emitters](https://banes-lab.com/anatomy/quality/folder-quality-core-emitters.md)
- [core/factories](https://banes-lab.com/anatomy/quality/folder-quality-core-factories.md)
- [core/predicates](https://banes-lab.com/anatomy/quality/folder-quality-core-predicates.md)

## Source

```typescript
import type { BaseContext, InstalledPlugin, TsTestScopeOptions } from "#types/eslint.types";
import type { ESLint, Linter } from "eslint";
import type { EslintRelaxScope, EslintRuleExclusion, EslintScopeConfig } from "#types/config.types";
import {
    HTML_FILES,
    JSON_PARSER,
    JSX,
    JS_ECOSYSTEMS,
    JS_FILES,
    LANGUAGE,
    LANG_FILES,
    LANG_PARSERS,
    MANIFEST_FILES,
    RULE_OFF,
    TS_FILES,
    TS_PREFIX,
    TS_TEST_FILES,
} from "#configuration/constants/eslint.constants";
import { emitEslintConfig, eslintRulesForConcepts } from "#core/emitters/eslint.emitter";
import { isParser, isPlugin, isRuleEntry } from "#core/predicates/eslint.predicate";
import { jsRulesOf, namespacesOf, pluginMap, toRulesRecord } from "#core/converters/eslint.converter";
import type { ConcernConfig } from "#types/concern.types";
import { isRecord } from "#core/selectors/record.selector";
import { loadModule } from "#core/loaders/eslint.loader";

const MANIFEST_CONFIG = "moduleManifest";
const TYPE_CHECK_OFF_CONFIG = "disable-type-checked";

const manifestRulesFrom = (plugin: unknown): Linter.RulesRecord => {
    if (!isRecord(plugin) || !isRecord(plugin["configs"])) {
        return {};
    }
    const manifest = plugin["configs"][MANIFEST_CONFIG];
    return isRecord(manifest) && isRecord(manifest["rules"]) ? toRulesRecord(manifest["rules"]) : {};
};

export const manifestScope = async (
    installed: InstalledPlugin[],
    plugins: Record<string, ESLint.Plugin>,
): Promise<Linter.Config | null> => {
    const rules: Linter.RulesRecord = {};
    for (const { plugin } of installed) {
        Object.assign(rules, manifestRulesFrom(plugin));
    }
    const parser = Object.keys(rules).length > 0 ? await loadModule(JSON_PARSER) : null;
    return isParser(parser) ? { files: MANIFEST_FILES, languageOptions: { parser }, plugins, rules } : null;
};

export const exclusionScopes = (exclusions: EslintRuleExclusion[]): Linter.Config[] =>
    exclusions.map((exclusion) => ({
        files: exclusion.eslintFiles,
        rules: Object.fromEntries(exclusion.rules.map((rule) => [rule, RULE_OFF])),
    }));

export const tsScopeOverride = (rules: Linter.RulesRecord): Linter.Config => {
    const off: Linter.RulesRecord = { "no-undef": RULE_OFF };
    for (const key of Object.keys(rules)) {
        const base = key.slice(TS_PREFIX.length);
        if (key.startsWith(TS_PREFIX) && Object.hasOwn(rules, base)) {
            off[base] = RULE_OFF;
        }
    }
    return { files: TS_FILES, rules: off };
};

export const disableTypeCheckedRules = (installed: InstalledPlugin[]): Linter.RulesRecord => {
    const tsPlugin = installed.find((entry) => entry.record.pluginNamespace === TS_PREFIX.slice(0, -1))?.plugin;
    if (!isRecord(tsPlugin) || !isRecord(tsPlugin["configs"])) {
        return {};
    }
    const disabled = tsPlugin["configs"][TYPE_CHECK_OFF_CONFIG];
    return isRecord(disabled) && isRecord(disabled["rules"]) ? toRulesRecord(disabled["rules"]) : {};
};

export const tsTestScope = (options: TsTestScopeOptions): Linter.Config | null =>
    isParser(options.tsParser)
        ? {
              files: [...TS_TEST_FILES, ...(options.extraGlobs ?? [])],
              languageOptions: {
                  ...options.language,
                  parser: options.tsParser,
                  parserOptions: { ...JSX, projectService: false },
              },
              rules: options.disabled,
          }
        : null;

export const baseConfigs = (context: BaseContext): Linter.Config[] => {
    const { plugins, rules, settings, tsParser, globals } = context;
    const language = { ...LANGUAGE, parserOptions: JSX, ...(globals ? { globals } : {}) };
    const withSettings = settings ? { settings } : {};
    const tsLanguage = isParser(tsParser) ? { parser: tsParser, parserOptions: { ...JSX, projectService: true } } : {};
    return [
        { plugins, ...withSettings },
        { files: TS_FILES, languageOptions: { ...language, ...tsLanguage }, rules, ...withSettings },
        { files: JS_FILES, languageOptions: language, rules: jsRulesOf(rules), ...withSettings },
    ];
};

const relaxScope = (scope: EslintRelaxScope): Linter.Config => ({
    files: scope.files,
    rules: Object.fromEntries(eslintRulesForConcepts(scope.relax).map((rule) => [rule, RULE_OFF])),
});

export const optionalScopes = (eslint: EslintScopeConfig): Linter.Config[] => {
    const relaxable = [eslint.generated, eslint.tests, eslint.buildScripts]
        .filter((scope): scope is EslintRelaxScope => Boolean(scope?.files.length))
        .map((scope) => relaxScope(scope));
    const { ignores } = eslint;
    const ignoreScope: Linter.Config[] = ignores && ignores.length > 0 ? [{ ignores }] : [];
    return [...ignoreScope, ...relaxable];
};

export const htmlScope = (
    rules: Linter.RulesRecord,
    settings: Linter.Config["settings"],
    htmlPlugin: unknown,
): Linter.Config | null =>
    isPlugin(htmlPlugin)
        ? {
              files: HTML_FILES,
              languageOptions: LANGUAGE,
              plugins: { html: htmlPlugin },
              rules,
              ...(settings ? { settings } : {}),
          }
        : null;

const namespaceRules = (activePlugins: string[], concerns: ConcernConfig): Linter.RulesRecord => {
    const namespaces = new Set(activePlugins);
    const out: Linter.RulesRecord = {};
    for (const [key, value] of Object.entries(emitEslintConfig({ activePlugins, concerns }))) {
        if (namespaces.has(key.split("/")[0] ?? "") && isRuleEntry(value)) {
            out[key] = value;
        }
    }
    return out;
};

const langScope = async (
    ecosystem: string,
    entries: InstalledPlugin[],
    concerns: ConcernConfig,
): Promise<Linter.Config | null> => {
    const files = LANG_FILES.get(ecosystem);
    const parser = files ? await loadModule(LANG_PARSERS.get(ecosystem) ?? "") : null;
    return isParser(parser)
        ? {
              ...(files ? { files } : {}),
              languageOptions: { parser },
              plugins: pluginMap(entries),
              rules: namespaceRules(namespacesOf(entries), concerns),
          }
        : null;
};

export const langScopes = async (installed: InstalledPlugin[], concerns: ConcernConfig): Promise<Linter.Config[]> => {
    const byEcosystem = new Map<string, InstalledPlugin[]>();
    for (const entry of installed.filter((item) => !JS_ECOSYSTEMS.has(item.record.ecosystem))) {
        byEcosystem.set(entry.record.ecosystem, [...(byEcosystem.get(entry.record.ecosystem) ?? []), entry]);
    }
    const scopes = await Promise.all(
        [...byEcosystem].map(async ([ecosystem, entries]) => langScope(ecosystem, entries, concerns)),
    );
    return scopes.filter((scope): scope is Linter.Config => scope !== null);
};
```
