@futdevpro/dynamo-eslint
v1.20.83
Published
Shared ESLint configs, Dynamo-powered plugin and validators for FutDevPro stacks
Readme
@futdevpro/dynamo-eslint
Shared ESLint presets, custom Dynamo rules, and validation tools for FutDevPro TypeScript projects. This package provides standardized linting configurations optimized for lint-on-save functionality across different project types.
🚀 Quick Start
Installation
npm install --save-dev @futdevpro/dynamo-eslintRequired Peer Dependencies
npm install --save-dev \
eslint@~9.39.5 \
[email protected] \
typescript-eslint@~8.67.0For Angular projects, also install:
npm install --save-dev \
angular-eslint@~20.7.0angular-eslint is an optional peer for Node-only consumers and is required when loading the ngx or
ngx-package preset.
📋 Available Presets
Test files are exempt (every preset, BFR-FLEET-014). In
*.spec.ts,*.test.ts(and.tsx,*.e2e-spec.ts) every type and code rule is off; the ONE rule tests keep is the name prefix —describe('| …'),it('| …')(@futdevpro/dynamo/require-test-description-prefix). The exemption is the last entry of every preset and is computed from the preset's own rules, so a rule added later is exempted in tests too. If you append your own entry for**/*.tsafter a preset, wrap the result again:require('@futdevpro/dynamo-eslint/test-file-exemption').withTestFileExemption([ ...preset, yourEntry ]).
1. base - Core TypeScript Rules
Generic TypeScript projects with browser environment.
const baseConfig = require('@futdevpro/dynamo-eslint/base');
module.exports = baseConfig;Features:
- Browser environment
- 1000 line limit
- All Dynamo custom rules enabled
- Explicit return types required
- A dropped promise is reported (
@typescript-eslint/no-floating-promises, type-aware, in every preset): an async call withoutawait,return,.catchorvoid. Mark intended fire-and-forget withvoid;Router.navigate/navigateByUrlare known safe (the router emits aNavigationErroritself)
2. nts - Node TypeScript Projects
Node.js TypeScript applications and services.
const ntsConfig = require('@futdevpro/dynamo-eslint/nts');
module.exports = ntsConfig;Features:
- Node environment
- 1500 line limit (extended for server code)
- Module source type
- All base rules + Node-specific optimizations
3. ngx - Angular Projects
Angular applications with component and template linting.
const ngxConfig = require('@futdevpro/dynamo-eslint/ngx');
module.exports = ngxConfig;Features:
- Angular ESLint integration
- Browser environment
- Component and template linting
- Angular-specific rules
4. fsm - Full Stack Module
Full-stack TypeScript modules (same as base).
const fsmConfig = require('@futdevpro/dynamo-eslint/fsm');
module.exports = fsmConfig;5. nts-package - Node TypeScript Packages
NPM packages and libraries built with TypeScript.
const ntsPackageConfig = require('@futdevpro/dynamo-eslint/nts-package');
module.exports = ntsPackageConfig;Features:
- Relaxed
@typescript-eslint/ban-typesandno-explicit-anyrules - Optimized for package development
- Extended line limits for library code
6. ngx-package - Angular Packages
Angular libraries and packages.
const ngxPackageConfig = require('@futdevpro/dynamo-eslint/ngx-package');
module.exports = ngxPackageConfig;⚙️ Lint-on-Save Configuration
Critical: Configure ESLint to run ONLY on save, not on type!
VSCode/Cursor Settings
Create .vscode/settings.json in your project root:
{
"eslint.run": "onSave",
"eslint.workingDirectories": [{ "mode": "auto" }],
"editor.codeActionsOnSave": {
"source.fixAll.eslint": "explicit"
},
"eslint.validate": [
"javascript",
"typescript"
]
}Workspace vs User Settings
- Workspace settings (
.vscode/settings.json): Apply only to current project - User settings: Apply globally to all projects
For team consistency, use workspace settings.
Multi-Root Workspace Configuration
For workspaces with multiple projects:
{
"eslint.run": "onSave",
"eslint.workingDirectories": [
{ "mode": "auto" },
{ "directory": "./packages/package-a" },
{ "directory": "./packages/package-b" }
],
"editor.codeActionsOnSave": {
"source.fixAll.eslint": "explicit"
}
}🔧 Custom Dynamo Rules
This package includes custom ESLint rules for Dynamo conventions:
Import Rules
@futdevpro/dynamo/import-order: Enforces consistent import ordering@futdevpro/dynamo/no-import-type: Forbids TypeScriptimport typesyntax@futdevpro/dynamo/no-js-import: Prevents importing.jsfiles in TypeScript
Naming Rules
@futdevpro/dynamo/naming-patterns: Enforces DyFM naming conventions
Type Rules
@futdevpro/dynamo/explicit-types: Enforces explicit type annotations on all TypeScript declarations (functions, variables, parameters, class properties, destructuring)
Security Rules
@futdevpro/dynamo/no-unlisted-inner-html(BFR-QA-005): XSS-surface guard — reports every[innerHTML]binding (.htmland inline.tstemplates) and everybypassSecurityTrust…(…)call, EXCEPT in reviewed files on theallowlist and up to the committed per-file baseline. So today's surfaces pass and every NEW one fails. Options:allow: string[](path suffixes) ·baselineFile(default.dynamo/xss-surface-baseline.json) ·escapePipe(path suffix of your escaping pipe — it must escape& < > "BEFORE producing HTML). Comments and*.spec.tsfiles are ignored.Adopt it in a project (run in the folder ESLint runs in, e.g.
client/):npx dynamo-xss-surface-baseline # writes .dynamo/xss-surface-baseline.json — commit it npx dynamo-xss-surface-baseline --check # exit 1 if the surface grew beyond the committed baselineThe
ngxpreset switches the rule on (error) only when that baseline file exists — without it nothing changes, so a dependency bump never breaks an existing lint step. Regenerate the baseline only after a reviewed change. ⚠️ A rule-level baseline on purpose: ESLint's own--suppress-ruleis applied only by the ESLint CLI, not by theESLintAPI thatng lintuses (measured on eslint 9.39.5).
UI Building-Block Rules
@futdevpro/dynamo/scroll-container-min-h-0(BFR-QA-004 ①): a scroll container (overflow-(y-)auto|scroll, or a component tag you list as a scroller) that will NOT scroll — between it and the element giving the chain its height (h-full, a fixed height) a column-flex item lacksmin-h-0, so the chain grows to the content height. Not reported: a scroll container with its own fixed height, an outer scroll container on the way, anabsolute/fixedancestor, a component host without classes in the template (itshost/:hoststyles are not visible), or no height source in the template at all. The chain is followed inside one template.@futdevpro/dynamo/module-load-failed-branch(BFR-QA-004 ②): a module (…/_modules/<name>/) whose templates show a loading state (…Loading…,inProgress_$,loading-skeleton) but none of which shows a failed state (@if (…error|…failed…), a…load-failed/…error-statecomponent, aload…error|failedtest id). A list OF errors (errors) is not a failed state.@futdevpro/dynamo/empty-state-requires-guard(BFR-QA-003): an empty-state message — a Hungarian "none / not yet" sentence (Nincs…,Még nincs…, also in an empty-state component'smessage) or a@for … @emptybranch — with no loading / failed guard: neither an ancestor@if/@else ifof the same template nor any template up the rendering parent chain guards it (inProgress,failed_$(),loading,skeleton,load-failed,…Loaded_$()). Unguarded, the user reads "none" WHILE the data loads and AFTER a failed load. Not reported: the empty-state component tag alone (it is also a "select something" placeholder), a permission denial ("Nincs jogosultság…"). Known limits: a guard passed through an[input], an i18n-bound message, a descriptive "Nincs…" sentence, an@emptyover a locally computed list — silence a NEW such case with<!-- eslint-disable-next-line @futdevpro/dynamo/empty-state-requires-guard -->. Counted per file in the same baseline (emptyStatesWithoutGuard); thengxpreset switches it on only when the baseline has that section, i.e. after you regenerate the baseline with a version that knows the rule.Adopt them in a project (run in the folder ESLint runs in, e.g.
client/):npx dynamo-ui-building-blocks-baseline # writes .dynamo/ui-building-blocks-baseline.json — commit it npx dynamo-ui-building-blocks-baseline --scroller-tag app-pull-to-refresh # a component that IS a scroll container npx dynamo-ui-building-blocks-baseline --check # exit 1 on a new caseExactly like the XSS guard: the
ngxpreset switches the first two rules on (error) only when that baseline file exists (the empty-state rule: only when it also has its section), so a dependency bump never breaks a--max-warnings=0lint step. The listed scroller tags live in the baseline'ssettings, so the rules and the generator always agree. Lower the baseline when you fix a case.
Unused Component Rule
@futdevpro/dynamo/no-unused-component(BFR-QA-006, project-wide): an Angular@Componentthat is built but used NOWHERE in the folder ESLint runs in — its selector is in no comment-free template (.html, or an inlinetemplate:in backticks or quotes) and nothing references its class in a usage position (route, lazy route,dialog.open(…), bootstrap,createComponent(…), an assignment…). NOT usages:import … fromlines (a route can disappear while its import stays),imports/declarations/exportsentries, the component's own file, specs and stories. When a reference exists only in a comment (// X_Component,,<!-- <app-x> -->) the message says a finished feature may be switched off. Meant for apps — in a library every public component is "unused" inside the package.Adopt it in a project (run in the folder ESLint runs in, e.g.
client/):npx dynamo-unused-components-baseline # writes .dynamo/unused-components-baseline.json — commit it npx dynamo-unused-components-baseline --check # exit 1 on a new unused componentSame gate as the other baseline guards: the
ngxpreset switches it on (error) only when that file exists. The project scan is cached per process and re-stamped every 3 s (editor lint-on-save).
🛠️ CLI Validation Tools
Use these commands to validate your codebase:
# Validate import ordering across all TypeScript files
dynamo-validate-imports
# Validate naming conventions across all TypeScript files
dynamo-validate-naming
# Audit ESLint configurations for duplicate rules
dynamo-eslintrc-audit
# Run comprehensive fixes
dynamo-fix
# Fix missing type annotations (functions, variables, parameters, class properties)
dynamo-fix-return-typesNPM Scripts Integration
Add to your package.json:
{
"scripts": {
"lint": "eslint src --ext .ts",
"lint:fix": "eslint src --ext .ts --fix",
"validate:imports": "dynamo-validate-imports",
"validate:naming": "dynamo-validate-naming",
"audit:eslintrc": "dynamo-eslintrc-audit",
"fix": "dynamo-fix",
"fix:types": "dynamo-fix-return-types"
}
}Strict lint gate — dynamo-lint-strict
A release/CI gate whose exit code comes from ESLint's aggregated counters (the project's own flat config):
dynamo-lint-strict [patterns...] [--max-warnings <n>] [--json]
dynamo-lint-strict src --max-warnings 0 # zero-warning gate
dynamo-lint-strict src --max-warnings 1200 # ratchet for a known warning backlog| exit | meaning |
|---|---|
| 0 | 0 errors (parsing errors included) and warnings within the budget |
| 1 | any error, or more warnings than --max-warnings |
| 2 | lint could not run — config error, no file matched, crash (never a pass) |
- Warning budget: without
--max-warningswarnings are reported but don't fail; set it explicitly for a gate. --json: the last stdout line is{ errorCount, fatalErrorCount, warningCount, fileCount, maxWarnings, legacyEslintignore, passed, exitCode }..eslintignoreis dead in ESLint 9 flat config — the files listed there ARE linted. The gate reports it loudly; move the patterns into anignoresentry ofeslint.config.*:module.exports = [ ...require('@futdevpro/dynamo-eslint/ngx'), { ignores: [ 'dist-electron/**' ] } ];
Explicit legacy boolean-name exceptions
dynamo-validate-naming is strict by default. A consumer that must preserve an existing persisted or public
contract can allow individual boolean property names in its package.json:
{
"dynamoEslint": {
"naming": {
"allowedBooleanPropertyNames": ["closed", "revoked"]
}
}
}Use this only for fields whose rename requires an explicit data/API migration. The validator rejects malformed configuration and continues to enforce every unlisted boolean property.
🔄 Migration from Manual Configs
Quick Migration Checklist
- Install the package and peer dependencies
- Replace your
.eslintrc.jsonwith the appropriate preset - Configure lint-on-save in VSCode/Cursor settings
- Run validation tools to check for issues
- Update CI/CD to use the new configuration
Common Migration Issues
- Peer dependency conflicts: Ensure all peer dependencies are installed
- Working directory issues: Use
"eslint.workingDirectories": [{ "mode": "auto" }] - Rule conflicts: The presets handle most conflicts automatically
📖 Detailed Migration Guide: See Migration Guide for step-by-step instructions.
🐛 Troubleshooting
Lint-on-Save Not Working
Check ESLint is running on save only:
{ "eslint.run": "onSave" // NOT "onType" }Verify working directories:
{ "eslint.workingDirectories": [{ "mode": "auto" }] }Check file associations:
{ "eslint.validate": ["javascript", "typescript"] }
Peer Dependency Issues
Ensure all peer dependencies are installed with compatible versions:
npm ls typescript-eslint angular-eslint eslint typescriptESLint v9 Flat Config
This package now uses ESLint v9 flat config format (eslint.config.js) for all exported presets. The package itself also uses flat config for self-linting.
📁 Examples
Reference implementations are available in the samples/ directory:
samples/base/eslint.config.js- Base preset examplesamples/nts/eslint.config.js- Node TypeScript examplesamples/ngx/eslint.config.js- Angular examplesamples/fsm/eslint.config.js- Full Stack Module examplesamples/nts-package/eslint.config.js- Node TypeScript Package examplesamples/ngx-package/eslint.config.js- Angular Package example
🔧 Recent Fixes
TypeScript AST Transformation Context Fix
The dynamo-fix-return-types script includes a fix for TypeScript AST transformation context issues. This resolves compilation errors when using ts.visitEachChild() by properly providing the TransformationContext parameter:
// Fixed implementation
return ts.visitEachChild(node, visit, {} as ts.TransformationContext);This ensures compatibility with TypeScript's AST transformation APIs.
Enhanced Type Annotation Fixing
The dynamo-fix-return-types script now handles comprehensive type annotation fixing:
- Function declarations: Adds return type annotations
- Arrow functions: Adds return type annotations
- Variable declarations: Adds type annotations based on initializer types
- Function parameters: Adds parameter type annotations
- Class properties: Adds property type annotations
The script uses TypeScript's type checker to infer appropriate types and automatically adds them to your code.
📚 Self-Linting
This package uses its own presets and rules for self-linting. The ESLint v9 flat config is in eslint.config.js and demonstrates advanced configuration patterns.
🏗️ Architecture
- Presets: Standardized rule configurations for different project types
- Custom Rules: Dynamo-specific linting rules for import order and naming
- CLI Tools: Validation and fixing utilities
- TypeScript Integration: Full TypeScript AST transformation support
Maintained by: Future Development Program Ltd.
License: ISC
Version: 1.20.3
