core/factories/eslint.scope.factory.ts
core/factories/eslint.scope.factory.ts is a file in GovLab Quality. 152 lines of code and 34 definitions.
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);
};