Skip to content

Blocks and rules ​

Each block is a function in lint/eslint/ returning named flat config objects. The presets spread them in the right order (a later block overrides an earlier one); a project only imports a preset.

Rules, by concept ​

lint/eslint/rules/ holds the rules of ESLint, typescript-eslint, SonarJS and import-x, one file per concept, so that a rule is found by what it checks, not by its plugin. Each concept file exports two configs: rules/<concept> for every JavaScript and TypeScript file, and rules/<concept>/typescript for the TypeScript files (the typescript-eslint rules, and the core rules they replace turned off).

ConceptWhat it checks
asyncPromises, async / await, timers: nothing floating, nothing forgotten.
browser-apisDOM, events, web APIs and the console.
classesConstructors, members, accessors, this, inheritance.
commentsWarning comments (TODO), comment style.
complexitySize and nesting limits (warnings).
conditionsif / else, ternaries, booleans, equality, exhaustive switches.
dead-codeUnused variables, unreachable code, useless statements.
errorsThrowing Error instances, catching, rejecting.
functionsDeclarations, parameters, return values, callbacks.
loopsfor, for…of, while, iteration.
modern-syntaxSpread, destructuring, template literals, const.
modulesimport / export, no require, private internal/ folders.
namingThe naming convention (camelCase, PascalCase types, UPPER_CASE).
numbersMagic numbers, NaN, parsing, precision.
objects-and-collections, arrays, stringsMethods and literals of each data type.
regular-expressionsValid, readable patterns with the v flag and named groups.
securityeval, implied eval, sanitizer bypasses.
typesNo any, no !, assertions, type definitions.
variablesDeclarations, shadowing, globals.
formattingPrettier, and the few layout rules outside it.
importsimport-x: declared dependencies, no cycle, order, folder imports.
file-namesKebab-case files and folders (local/).
exportsOne exported function or class per source file (local/).
disable-commentsThe policy of the eslint-disable comments (local/).
test-codeSonarJS test rules, and the relaxations specs and benchmarks need.
node-apis, other-frameworksSonarJS rules on Node APIs, and on frameworks not used here (all off).

SonarJS, in the same files ​

The SonarJS rules sit in the concept files too, under // ---- SonarJS ----: the Sonar way profile of SonarQube is on, the other rules are off (Off: not in the Sonar way profile). A SonarJS rule that duplicates a core rule stays off (Off: duplicate of …): the code already meets it, and a mistake gives one message, not two. The few SonarJS rules that measure differently (cognitive complexity instead of complexity) replace the core rule, which is off (Off: replaced by sonarjs/…). See Standards and SonarQube.

Modes, library, frameworks ​

These blocks sit in lint/eslint/rules/ too; the presets choose them:

BlockAdded byWhat it does
nodetypescript-node (the root)Node globals and modules, tool default exports, imports with extension, devDependencies.
browserevery project preset, on sourceFilesBrowser globals, no Node module, folder imports without extension, no stray console.
libraryangular-library, typescript-browser with isLibraryany and void free in a generic API.
angular-componentsangular-library, angular-appComponents, directives, pipes, services, signals, inject(), OnPush.
angular-templatesangular-library, angular-appControl flow, bindings, complexity of the templates; accessibility and i18n off.
storybookstorybookPackageDirectoryOnly what breaks a story or lies in a test; default exports and PascalCase stories allowed.

Screen-reader accessibility and @angular/localize are not used (simulators, texts translated with Transloco): their rules are listed and off, except what is odd anyway (autofocus, a positive tabindex: warnings).

The rules written here ​

lint/eslint/rules/local/ is a small plugin for what no plugin does the way the presets need:

RuleWhat it checks
local/kebab-case-pathKebab-case files and folders; dots separate words, dot folders are skipped.
local/export-matches-filenameOne exported function or class per file, named after it (a warning); its types may stay.
local/import-foldersImports through a folder's index: required in the sources, simplified on save (autofix).
local/disable-only-warningsAn eslint-disable names its rules, and only rules set to warn.
local/disable-reasonA reason after -- (a warning).
local/disable-next-line-onlyOnly eslint-disable-next-line; no block or file disable, no inline config.
mjs
// An `eslint-disable` comment names its rules, and only rules set to `warn`: an `error` cannot be disabled, the
// code is fixed instead. The severity of each rule comes from the resolved config of the file, mirrored in
// `settings.local.ruleSeverities` by lint/eslint/setup/rule-severities.mjs (the presets do it).

const ERROR = 2;

export default {
  meta: {
    type: 'problem',
    docs: { description: 'Allow `eslint-disable` comments only for rules set to `warn`.' },
    schema: [],
    messages: {
      unnamed: 'Name the rules to disable: `eslint-disable-next-line <rule> -- <reason>`.',
      error: "'{{rule}}' is an error: it cannot be disabled. Fix the code.",
    },
  },
  create(context) {
    const severities = context.settings.local?.ruleSeverities ?? {};
    return {
      Program() {
        const { directives } = context.sourceCode.getDisableDirectives();
        for (const directive of directives.filter(({ type }) => type !== 'enable')) {
          const rules = directive.value
            .split(',')
            .map((rule) => rule.trim())
            .filter((rule) => rule !== '');
          if (rules.length === 0) {
            context.report({ loc: directive.node.loc, messageId: 'unnamed' });
          }
          for (const rule of rules.filter((name) => severities[name] === ERROR)) {
            context.report({ loc: directive.node.loc, messageId: 'error', data: { rule } });
          }
        }
      },
    };
  },
};

Remove a plugin ​

A project that does not want a plugin removes it after its preset, by keyword, with setup/without-plugins.mjs: withoutPlugins(angularPreset({ … }), ['sonarjs']). The rules turned off because the removed plugin covered them come back on, so nothing is left unchecked.

Generated from the JSDoc of the sources.