@helfy/helfy-lint
Current release: 0.2.0. See release notes.
ESLint rules and a Prettier plugin for Helfy apps. The rules enforce
the layer model: @Store holds state, @Service does infrastructure work, @UseCase orchestrates both, @View
renders.
npm install -D @helfy/helfy-lint eslint @typescript-eslint/parser
ESLint
// eslint.config.mjs
import helfy from "@helfy/helfy-lint";
import tsParser from "@typescript-eslint/parser";
export default [
{
files: ["src/**/*.ts"],
languageOptions: { parser: tsParser },
...helfy.configs.recommended,
},
];
| Rule | In recommended |
|---|---|
helfy/store/no-inject — no @inject / @useCtx / @useForm on @Store | error |
helfy/store/no-ctor-params — @Store has an empty constructor | error |
helfy/store/no-effect — no @effect on @Store | error |
helfy/store/computed-only-on-getter — @computed only on getters | error |
helfy/store/no-http-calls — no HTTP, storage or timer I/O in @Store | error |
helfy/service/no-state, no-computed, no-effect — no reactivity in @Service | error |
helfy/service/no-field-inject — dependencies through the constructor | error |
helfy/use-case/no-reactive-decorators — no @state / @computed / @effect in @UseCase | error |
helfy/use-case/no-field-inject — dependencies through the constructor | error |
helfy/layers/no-store-imports-service, no-store-imports-use-case, no-service-imports-use-case | error |
helfy/view/no-ctor-di — @View takes dependencies with @inject, not the constructor | error |
helfy/context/no-ctor-di — @Context / @Form constructors receive props only | error |
helfy/view/no-destructure-props — props destructuring outside tracked methods and direct prop snapshots in field initializers | warn |
helfy/decorators/no-ref-state — no @ref together with @state on one field | error |
helfy/decorators/synchronous-effect — no async or generator @effect methods | warn |
helfy/imports/no-deep-helfy-imports — no @helfy/helfy/dist/... | error |
helfy/imports/no-unknown-helfy-subpath — only documented @helfy/helfy/* entries | error |
Decorator checks resolve named import aliases and namespace imports from Helfy, respect lexical shadowing, and ignore decorators imported from other libraries. Bare decorator names remain supported when no local binding exists. Re-exports through application barrels are not resolved.
Layer and Store HTTP import checks also cover exported classes and class expressions. Layer classification still uses import paths and names rather than a TypeScript dependency graph. Public-subpath checks include dynamic imports with string literals.
view/no-destructure-props allows destructuring directly inside synchronous @effect methods and @computed
getters, where reads are tracked. It warns about direct field snapshots such as title = this.props.title;
use a getter or @computed instead. It does not follow aliases or perform data-flow analysis.
@useForm is context injection (it delegates to @useCtx) and is also forbidden on Service and UseCase fields.
The effect runtime tracks synchronous reads and ignores method return values. Read dependencies before starting
async work and handle promise rejection explicitly. synchronous-effect checks declared async/generator methods;
it does not infer promises returned by ordinary methods.
Type-checked layer dependencies
For projects with a tsconfig.json, use the optional preset:
export default [
{
files: ["src/**/*.ts"],
...helfy.configs["recommended-type-checked"],
languageOptions: {
parser: tsParser,
parserOptions: { projectService: true },
},
},
];
This preset replaces the three heuristic layers/* rules with helfy/layers/no-invalid-dependencies (error).
It uses TypeScript symbol identities to detect Store → Service/UseCase and Service → UseCase imports, including
aliases, default exports, class expressions, re-export barrels and accessed namespace members. Type-only imports
are allowed; names such as ThingService alone do not determine a class's layer. A file containing a layer class
is checked as a whole, as with the heuristic rules.
The typed rule checks static value imports and namespace member accesses, not dynamic imports or a transitive
dependency graph. It requires parser services (projectService: true or project: "./tsconfig.json"); it is not
enabled by the ordinary recommended preset. TypeScript is a peer dependency. Other decorator rules keep the
local import resolution described above.
Build-time checks
@helfy/helfy/compiler/helfy-ts-loader already fails the build on the rules that are never valid (store/no-inject,
store/no-effect, store/no-ctor-params, service/no-state, service/no-computed, service/no-effect,
service/no-field-inject, use-case/no-reactive-decorators, use-case/no-field-inject,
decorators/no-ref-state, imports/no-deep-helfy-imports); this package is not needed for that. ESLint remains the single source for editor and
CI diagnostics.
The Context/Form constructor, synchronous-effect and typed dependency checks are ESLint-only. Build-time checks
resolve local import aliases, namespace imports and lexical shadowing within the current file; they do not load
a TypeScript project or follow re-export barrels. Deep import checks include re-exports and literal import().
Compiler tests compare the compile-fail rule catalog, messages and representative diagnostics with ESLint.
Prettier for .hf
Semantic .hf checks belong to helfy-check in the language-server package, rather than ESLint. In this
repository run npm run check:templates after installing that package's dependencies. It checks syntax and
types using the editor's virtual documents and returns a failing exit code for errors. See the
CI setup and CLI options.
// prettier.config.cjs
module.exports = {
plugins: [require.resolve("@helfy/helfy-lint/prettier")],
overrides: [{ files: "*.hf", options: { parser: "helfy-hf" } }],
};
The formatter is the one the Helfy language server uses. A file with syntax errors is left as is.
Roadmap
Future linting work and priorities: roadmap.
License
MIT