@jskit-ai/config-eslint 0.1.241 → 0.1.243

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. package/README.md +135 -0
  2. package/base.js +5 -1
  3. package/clientFeatures.js +87 -0
  4. package/formatter.js +60 -0
  5. package/index.js +4 -0
  6. package/package.json +8 -1
  7. package/plugin/factoryResults.js +162 -0
  8. package/plugin/index.js +64 -0
  9. package/plugin/packageSources.js +133 -0
  10. package/plugin/rules/actionCreator.js +55 -0
  11. package/plugin/rules/actionExecuteShape.js +75 -0
  12. package/plugin/rules/allowCommentForm.js +145 -0
  13. package/plugin/rules/appErrorCode.js +169 -0
  14. package/plugin/rules/collaboratorProps.js +130 -0
  15. package/plugin/rules/fixedResultShape.js +96 -0
  16. package/plugin/rules/frozenFactoryResult.js +44 -0
  17. package/plugin/rules/jsdocDescriptionInside.js +45 -0
  18. package/plugin/rules/namedRepeatedType.js +178 -0
  19. package/plugin/rules/noDualShape.js +61 -0
  20. package/plugin/rules/noDuplicateHelper.js +146 -0
  21. package/plugin/rules/noEnvDefault.js +33 -0
  22. package/plugin/rules/noFactorySpread.js +99 -0
  23. package/plugin/rules/noFrozenVueState.js +48 -0
  24. package/plugin/rules/noIdempotency.js +26 -0
  25. package/plugin/rules/noModuleRegistry.js +61 -0
  26. package/plugin/rules/noModuleState.js +50 -0
  27. package/plugin/rules/noOptionalAfterDefault.js +91 -0
  28. package/plugin/rules/noPatchedError.js +50 -0
  29. package/plugin/rules/noSlotSetter.js +40 -0
  30. package/plugin/rules/noTestOnlyExport.js +38 -0
  31. package/plugin/rules/oneLineAction.js +98 -0
  32. package/plugin/rules/onePropertyPerLine.js +86 -0
  33. package/plugin/rules/releasedListeners.js +132 -0
  34. package/plugin/rules/requiredCollaborators.js +150 -0
  35. package/plugin/rules/typedDependencies.js +46 -0
  36. package/plugin/rules/typedInject.js +96 -0
  37. package/plugin/support.js +377 -0
  38. package/plugin/vueSources.js +59 -0
  39. package/typedFeatures.js +104 -0
package/README.md ADDED
@@ -0,0 +1,135 @@
1
+ # @jskit-ai/config-eslint
2
+
3
+ Shared flat ESLint presets for JSKIT projects.
4
+
5
+ ```js
6
+ // eslint.config.mjs
7
+ import {
8
+ baseConfig,
9
+ clientFeaturesConfig,
10
+ formatterConfig,
11
+ nodeConfig,
12
+ sizeLimitsConfig,
13
+ typedFeaturesConfig,
14
+ vueConfig,
15
+ webConfig
16
+ } from "@jskit-ai/config-eslint/server";
17
+
18
+ const SERVER_SOURCES = ["packages/*/src/server/**", "packages/*/src/shared/**", "server/**", "server.js"];
19
+ const CLIENT_SOURCES = ["packages/*/src/client/**", "src/**"];
20
+ const HAND_WRITTEN_SOURCES = ["packages/*/src/**", "src/**", "config/**", "server/**", "server.js", "bin/**"];
21
+
22
+ export default [
23
+ ...baseConfig,
24
+ ...typedFeaturesConfig(SERVER_SOURCES),
25
+ ...sizeLimitsConfig(HAND_WRITTEN_SOURCES),
26
+ ...vueConfig,
27
+ ...clientFeaturesConfig(CLIENT_SOURCES),
28
+ ...webConfig,
29
+ ...nodeConfig,
30
+ ...formatterConfig([...SERVER_SOURCES, "config/**", "bin/**"])
31
+ ];
32
+ ```
33
+
34
+ - `baseConfig`: ESLint's recommended rules, JSKIT's import policy and, as
35
+ warnings on `**/src/server/**`, the typed-features rules.
36
+ - `typedFeaturesConfig(globs)`: the typed-features rules and the size limits,
37
+ as errors, for the server and shared code the globs select.
38
+ - `clientFeaturesConfig(globs)`: the client rules, as errors, for the client
39
+ code the globs select, Vue components included; list it after `vueConfig`.
40
+ - `sizeLimitsConfig(globs)`: files under 750 lines and functions under 600, as
41
+ errors, for every hand-written file the globs select, client code and Vue
42
+ components included.
43
+ - `formatterConfig(globs)`: the formatter.
44
+ - `nodeConfig`, `webConfig`: Node and browser globals.
45
+ - `vueConfig`: `eslint-plugin-vue`'s recommended rules.
46
+
47
+ ## Formatter
48
+
49
+ `formatterConfig(globs)` is the one formatter config of a JSKIT application, as
50
+ errors, for the source files the globs select (tests are not formatted):
51
+
52
+ ```js
53
+ // eslint.config.mjs
54
+ import { baseConfig, formatterConfig, typedFeaturesConfig } from "@jskit-ai/config-eslint/server";
55
+
56
+ const SERVER_SOURCES = ["packages/*/src/server/**", "packages/*/src/shared/**", "server/**", "server.js"];
57
+
58
+ export default [
59
+ ...baseConfig,
60
+ ...typedFeaturesConfig(SERVER_SOURCES),
61
+ // Published upgrade scripts never change, so the formatter leaves them alone.
62
+ ...formatterConfig(SERVER_SOURCES, { ignores: ["packages/core/src/server/upgrades/**"] })
63
+ ];
64
+ ```
65
+
66
+ - Width 140 for code, comments and JSDoc prose. Strings, template literals,
67
+ regular expressions, URLs, `readable-check: allow` comments, ESLint
68
+ directives and JSDoc type tags may run past it, so long prose stays inline.
69
+ - Semicolons, double quotes (template literals allowed), no trailing commas,
70
+ no space before a named function's parentheses, no repeated or trailing
71
+ spaces.
72
+ - A one-line body without braces stays beside its `if` (`if (!x) return null;`);
73
+ an `if`/`else` chain braces all its bodies or none, and a line holds at most
74
+ two statements.
75
+ - An object whose properties span lines puts each property on its own line,
76
+ with its braces on their own lines; a call whose arguments span lines breaks
77
+ after `(` and before `)`.
78
+ - A braced block spans lines (`1tbs`: `} else {` on the closing brace's line),
79
+ and code is indented by two spaces, `case` inside `switch` by one level and a
80
+ ternary's object, array or function branch past its `?`.
81
+
82
+ `eslint --fix` fixes all of these except the width and the statements per line,
83
+ and its output passes them: a block it braces or an argument list it breaks is
84
+ re-indented in the same run.
85
+
86
+ ## Typed features
87
+
88
+ The `jskit` plugin checks the rules of the
89
+ [typed features guide](https://mobily-enterprises.github.io/jskit-ai/guide/framework/typed-features),
90
+ the one place JSKIT explains how server code is written. Its
91
+ [lint rules table](https://mobily-enterprises.github.io/jskit-ai/guide/framework/typed-features#lint-rules)
92
+ lists what each rule reports, every finding ends with a link to the section
93
+ that explains it, and its
94
+ [exceptions](https://mobily-enterprises.github.io/jskit-ai/guide/framework/typed-features#exceptions)
95
+ give the `readable-check: allow` comment and its reason prefixes.
96
+
97
+ `baseConfig` reports the rules as warnings, so adopting a new version does not
98
+ fail a build. `typedFeaturesConfig(globs)` makes them errors for the globs you
99
+ list; an existing application adds each package as it converts it. The rules
100
+ cover every module a capability value is built from, so list a package's
101
+ shared modules too (`packages/billing/src/shared/**`). Test files and `test`,
102
+ `tests`, `client`, `components` and `pages` directories are not checked.
103
+
104
+ ## Client features
105
+
106
+ `clientFeaturesConfig(globs, severity = "error")` holds composables, Vue
107
+ components, client factories and client providers to the
108
+ [client features guide](https://mobily-enterprises.github.io/jskit-ai/guide/framework/client-features):
109
+ the typed-features rules that read client code, with `use*` composables
110
+ counted as factories and component templates read for probes and optional
111
+ calls, plus `typed-inject`, `collaborator-props`, `fixed-result-shape`,
112
+ `no-frozen-vue-state`, `released-listeners`, `no-module-state` and
113
+ `eslint-plugin-vue`'s prop and emit rules. The server's action and error rules
114
+ do not apply, a composable is never held to `Object.freeze`, and
115
+ `no-duplicate-helper` compares a client helper with the whole package. Nothing
116
+ changes for client code until a glob selects it, so an existing application
117
+ lists each client tree as it converts it. In a template, one
118
+ `<!-- readable-check: allow <rule> - <reason> -->` above an element excuses
119
+ that element's bindings.
120
+
121
+ `sizeLimitsConfig(globs, { ignores })` holds the rest of the hand-written code
122
+ to the size limits; tests are not checked, and `ignores` leaves generated
123
+ output alone. A generated or static data module is exempt through its first
124
+ line, `/* eslint-disable max-lines -- data: <how it is produced> */`.
125
+
126
+ `one-line-action` leaves CRUD custom actions' `execute({ input, service, … })`
127
+ alone, also when it takes only part of that object (`({ service, context })`):
128
+ that is the CRUD declaration's own contract. `no-duplicate-helper` and
129
+ `named-repeated-type` compare a file with the rest of its package. Both treat a
130
+ pattern's example application (an `example` directory beside a `PATTERN.md`)
131
+ as a package of its own, skip `templates` and `patterns` directories inside a
132
+ package, and leave the agent skill bundle's generated copies under
133
+ `skills/jskit/references/patterns` to their sources. `app-error-code` follows
134
+ the error classes a module declares, and compares codes and sentences within
135
+ one module.
package/base.js CHANGED
@@ -1,4 +1,5 @@
1
1
  import js from "@eslint/js";
2
+ import { typedFeaturesConfig } from "./typedFeatures.js";
2
3
 
3
4
  const SOURCE_FILES = "**/*.{js,mjs,cjs,vue}";
4
5
 
@@ -43,7 +44,10 @@ const baseConfig = Object.freeze([
43
44
  }
44
45
  ]
45
46
  }
46
- }
47
+ },
48
+ // Server code is reported against the typed-features rules; an application
49
+ // makes them errors with typedFeaturesConfig as it converts each package.
50
+ ...typedFeaturesConfig(["**/src/server/**"], "warn")
47
51
  ]);
48
52
 
49
53
  export { baseConfig };
@@ -0,0 +1,87 @@
1
+ import vuePlugin from "eslint-plugin-vue";
2
+ import vueParser from "vue-eslint-parser";
3
+ import { jskitPlugin } from "./plugin/index.js";
4
+ import { sizeLimitRules } from "./typedFeatures.js";
5
+
6
+ const CLIENT_SOURCE_FILES = "**/*.{js,mjs,vue}";
7
+ const COMPONENT_FILES = "**/*.vue";
8
+ const TEST_FILES = Object.freeze([
9
+ "**/{test,tests}/**",
10
+ "**/*.{test,spec,vitest}.*"
11
+ ]);
12
+
13
+ // The typed-features rules that read client code, and the client-only rules, at one severity: collaborators of
14
+ // composables, client factories and providers are typed where received and required, never probed, in scripts and
15
+ // templates; injected values take an exported key, a type and no empty default; props a component calls are required
16
+ // and typed by their contract; results keep the shape they are built with and hold Vue state unfrozen; whatever client
17
+ // code starts it releases; no module-level mutable state; one copy of each helper per package; files under 750 lines
18
+ // and functions under 600. The server's action and error rules have no client counterpart, and a composable's result is
19
+ // never held to Object.freeze.
20
+ function clientFeaturesRules(severity) {
21
+ return Object.freeze({
22
+ "jskit/typed-dependencies": severity,
23
+ "jskit/required-collaborators": severity,
24
+ "jskit/no-slot-setter": severity,
25
+ "jskit/no-module-registry": severity,
26
+ "jskit/no-duplicate-helper": severity,
27
+ "jskit/allow-comment-form": severity,
28
+ "jskit/jsdoc-description-inside": severity,
29
+ "jskit/no-dual-shape": severity,
30
+ "jskit/named-repeated-type": severity,
31
+ "jskit/no-factory-spread": severity,
32
+ "jskit/frozen-factory-result": severity,
33
+ "jskit/no-optional-after-default": severity,
34
+ "jskit/one-property-per-line": severity,
35
+ "jskit/no-env-default": severity,
36
+ "jskit/no-test-only-export": severity,
37
+ "jskit/typed-inject": severity,
38
+ "jskit/collaborator-props": severity,
39
+ "jskit/fixed-result-shape": severity,
40
+ "jskit/released-listeners": severity,
41
+ "jskit/no-module-state": severity,
42
+ "jskit/no-frozen-vue-state": severity,
43
+ ...sizeLimitRules(severity)
44
+ });
45
+ }
46
+
47
+ // Every prop has a type and is required or defaulted (never both), and every emitted event is declared.
48
+ function componentRules(severity) {
49
+ return Object.freeze({
50
+ "vue/require-prop-types": severity,
51
+ "vue/require-default-prop": severity,
52
+ "vue/require-valid-default-prop": severity,
53
+ "vue/no-required-prop-with-default": severity,
54
+ "vue/require-explicit-emits": severity
55
+ });
56
+ }
57
+
58
+ /**
59
+ * The typed-features rules for the client source files the globs select (modules and Vue components; tests excluded),
60
+ * as errors unless another severity is given. An application lists each client tree as it converts it, so the rules
61
+ * it already follows stay followed; nothing else changes until it does.
62
+ * @param {string[]} files
63
+ * @param {"error" | "warn" | "off"} [severity]
64
+ */
65
+ function clientFeaturesConfig(files, severity = "error") {
66
+ if (!Array.isArray(files) || files.length === 0) {
67
+ throw new TypeError("clientFeaturesConfig requires a list of file globs.");
68
+ }
69
+ return Object.freeze([
70
+ Object.freeze({
71
+ files: files.map((pattern) => [pattern, CLIENT_SOURCE_FILES]),
72
+ ignores: TEST_FILES,
73
+ plugins: { jskit: jskitPlugin },
74
+ settings: { jskit: { client: true } },
75
+ rules: clientFeaturesRules(severity)
76
+ }),
77
+ Object.freeze({
78
+ files: files.map((pattern) => [pattern, COMPONENT_FILES]),
79
+ ignores: TEST_FILES,
80
+ plugins: { vue: vuePlugin },
81
+ languageOptions: { parser: vueParser },
82
+ rules: componentRules(severity)
83
+ })
84
+ ]);
85
+ }
86
+
87
+ export { clientFeaturesConfig };
package/formatter.js ADDED
@@ -0,0 +1,60 @@
1
+ const SOURCE_FILES = "**/*.{js,mjs}";
2
+ // Lines that may run past the width: allow comments, ESLint directives and JSDoc type tags, whose text cannot wrap.
3
+ const LONG_TEXT_PATTERN = "readable-check: allow|eslint-disable|@typedef|@type \\{|@param \\{|@property \\{|@returns \\{";
4
+ const NOT_FORMATTED = Object.freeze([
5
+ "**/{test,tests}/**",
6
+ "**/*.test.*"
7
+ ]);
8
+
9
+ const FORMATTER_RULES = Object.freeze({
10
+ // Code, comments and JSDoc prose wrap at 140 characters; long prose stays inline in strings and templates.
11
+ "max-len": ["error", {
12
+ code: 140,
13
+ ignoreUrls: true,
14
+ ignoreStrings: true,
15
+ ignoreTemplateLiterals: true,
16
+ ignoreRegExpLiterals: true,
17
+ ignorePattern: LONG_TEXT_PATTERN
18
+ }],
19
+ semi: ["error", "always"],
20
+ quotes: ["error", "double", { avoidEscape: true, allowTemplateLiterals: true }],
21
+ "comma-dangle": ["error", "never"],
22
+ "space-before-function-paren": ["error", { anonymous: "ignore", named: "never", asyncArrow: "ignore" }],
23
+ // One layout: a one-line `if (x) return y;`, or braces; one property per line once an object spans lines.
24
+ curly: ["error", "multi-line", "consistent"],
25
+ "nonblock-statement-body-position": ["error", "beside"],
26
+ "object-curly-newline": ["error", { multiline: true, consistent: true }],
27
+ "object-property-newline": ["error", { allowAllPropertiesOnSameLine: true }],
28
+ "function-paren-newline": ["error", "multiline-arguments"],
29
+ "max-statements-per-line": ["error", { max: 2 }],
30
+ "no-multi-spaces": "error",
31
+ "no-trailing-spaces": "error",
32
+ // A braced block spans lines, `} else {` stays on the closing brace's line, and two-space indentation re-indents
33
+ // what the other fixes break onto new lines. A ternary's object, array or function branch is indented past its `?`.
34
+ "brace-style": ["error", "1tbs"],
35
+ "block-spacing": ["error", "always"],
36
+ indent: ["error", 2, { SwitchCase: 1, offsetTernaryExpressions: true }]
37
+ });
38
+
39
+ /**
40
+ * The one formatter config of a JSKIT application, as errors, for the source files the globs select: width 140,
41
+ * semicolons, double quotes, no trailing commas, one property per line in an object that spans lines, a one-line
42
+ * `if` only without braces, braced blocks on their own lines and two-space indentation. Tests are not formatted.
43
+ * @param {string[]} files
44
+ * @param {{ ignores?: string[] }} [options] Files the formatter never touches, such as published scripts that must not
45
+ * change.
46
+ */
47
+ function formatterConfig(files, { ignores = [] } = {}) {
48
+ if (!Array.isArray(files) || files.length === 0) {
49
+ throw new TypeError("formatterConfig requires a list of file globs.");
50
+ }
51
+ return Object.freeze([
52
+ Object.freeze({
53
+ files: files.map((pattern) => [pattern, SOURCE_FILES]),
54
+ ignores: [...NOT_FORMATTED, ...ignores],
55
+ rules: FORMATTER_RULES
56
+ })
57
+ ]);
58
+ }
59
+
60
+ export { formatterConfig };
package/index.js CHANGED
@@ -2,3 +2,7 @@ export { baseConfig } from "./base.js";
2
2
  export { nodeConfig } from "./node.js";
3
3
  export { webConfig } from "./web.js";
4
4
  export { vueConfig } from "./vue.js";
5
+ export { sizeLimitsConfig, typedFeaturesConfig } from "./typedFeatures.js";
6
+ export { clientFeaturesConfig } from "./clientFeatures.js";
7
+ export { formatterConfig } from "./formatter.js";
8
+ export { jskitPlugin } from "./plugin/index.js";
package/package.json CHANGED
@@ -1,15 +1,19 @@
1
1
  {
2
2
  "name": "@jskit-ai/config-eslint",
3
- "version": "0.1.241",
3
+ "version": "0.1.243",
4
4
  "description": "Shared flat ESLint presets for JSKIT projects.",
5
5
  "type": "module",
6
6
  "files": [
7
7
  "src",
8
+ "plugin",
8
9
  "base.js",
9
10
  "index.js",
10
11
  "node.js",
11
12
  "web.js",
12
13
  "vue.js",
14
+ "typedFeatures.js",
15
+ "clientFeatures.js",
16
+ "formatter.js",
13
17
  "README.md"
14
18
  ],
15
19
  "scripts": {
@@ -21,6 +25,9 @@
21
25
  "./node": "./node.js",
22
26
  "./web": "./web.js",
23
27
  "./vue": "./vue.js",
28
+ "./typed-features": "./typedFeatures.js",
29
+ "./client-features": "./clientFeatures.js",
30
+ "./formatter": "./formatter.js",
24
31
  "./server": "./src/server/index.js",
25
32
  "./client": "./src/client/index.js"
26
33
  },
@@ -0,0 +1,162 @@
1
+ import { isFreezeCall } from "./support.js";
2
+
3
+ // What the factory rules share: a function's defined name, what it returns,
4
+ // and whether an object it returns holds behaviour or only data.
5
+
6
+ const CREATE_NAME = /^create[A-Z]/u;
7
+ const FUNCTION_TYPES = new Set(["ArrowFunctionExpression", "FunctionExpression", "FunctionDeclaration"]);
8
+ // Vue's calls that make reactive state. A value holding their results must stay extensible: reactive() returns a
9
+ // frozen object unproxied, so its refs stop unwrapping, and proxyRefs() (which Vue also applies to a setup() result
10
+ // and to defineExpose()) throws reading a ref of a frozen object.
11
+ const VUE_STATE_CALLS = new Set([
12
+ "ref", "shallowRef", "computed", "reactive", "shallowReactive", "readonly", "shallowReadonly", "toRef", "toRefs",
13
+ "customRef", "useTemplateRef", "useModel", "defineModel"
14
+ ]);
15
+
16
+ // Whether a call makes Vue reactive state.
17
+ function isVueStateCall(node) {
18
+ return node?.type === "CallExpression" && node.callee.type === "Identifier" && VUE_STATE_CALLS.has(node.callee.name);
19
+ }
20
+
21
+ // Whether a function makes Vue reactive state anywhere in its body: a composable or a client runtime, whose result
22
+ // callers may hand to reactive() or proxyRefs().
23
+ function makesVueState(sourceCode, fn) {
24
+ let found = false;
25
+ const visit = (node) => {
26
+ if (found || !node || typeof node.type !== "string") {
27
+ return;
28
+ }
29
+ if (isVueStateCall(node)) {
30
+ found = true;
31
+ return;
32
+ }
33
+ for (const key of sourceCode.visitorKeys[node.type] || []) {
34
+ [node[key]].flat().forEach(visit);
35
+ }
36
+ };
37
+ visit(fn.body);
38
+ return found;
39
+ }
40
+
41
+ // The name a function is defined under: its own, its variable's, its property's
42
+ // or the member it is assigned to.
43
+ function definedName(fn) {
44
+ if (fn.id?.name) {
45
+ return fn.id.name;
46
+ }
47
+ const parent = fn.parent;
48
+ if (parent?.type === "VariableDeclarator" && parent.init === fn) {
49
+ return parent.id.name || "";
50
+ }
51
+ if ((parent?.type === "Property" || parent?.type === "MethodDefinition") && parent.value === fn) {
52
+ return parent.key?.name || parent.key?.value || "";
53
+ }
54
+ if (parent?.type === "AssignmentExpression" && parent.right === fn) {
55
+ return parent.left.property?.name || parent.left.name || "";
56
+ }
57
+ return "";
58
+ }
59
+
60
+ // The function a return statement returns from.
61
+ function enclosingFunction(sourceCode, node) {
62
+ return sourceCode.getAncestors(node).findLast((ancestor) => FUNCTION_TYPES.has(ancestor.type)) || null;
63
+ }
64
+
65
+ // The expressions a function returns: each return statement's argument, or an
66
+ // arrow's expression body. Used from a ReturnStatement or an arrow visitor.
67
+ function returnedFrom(sourceCode, node) {
68
+ if (node.type === "ReturnStatement") {
69
+ const fn = enclosingFunction(sourceCode, node);
70
+ return fn && node.argument ? { fn, returned: node.argument } : null;
71
+ }
72
+ return node.body.type === "BlockStatement" ? null : { fn: node, returned: node.body };
73
+ }
74
+
75
+ function findVariable(scope, name) {
76
+ for (let current = scope; current; current = current.upper) {
77
+ const variable = current.set.get(name);
78
+ if (variable) {
79
+ return variable;
80
+ }
81
+ }
82
+ return null;
83
+ }
84
+
85
+ // The single definition of the variable an identifier reads, or null.
86
+ function definitionOf(sourceCode, identifier) {
87
+ const variable = findVariable(sourceCode.getScope(identifier), identifier.name);
88
+ return variable?.defs.length === 1 ? variable.defs[0] : null;
89
+ }
90
+
91
+ function isInlineFunction(property) {
92
+ return property.type === "Property" && property.kind === "init" && (property.method || FUNCTION_TYPES.has(property.value.type));
93
+ }
94
+
95
+ // A member that refers to a function the module or factory defines.
96
+ function refersToFunction(sourceCode, property) {
97
+ if (property.type !== "Property" || property.value.type !== "Identifier") {
98
+ return false;
99
+ }
100
+ const definition = definitionOf(sourceCode, property.value);
101
+ return definition?.type === "FunctionName" ||
102
+ (definition?.type === "Variable" && FUNCTION_TYPES.has(definition.node.init?.type));
103
+ }
104
+
105
+ // An object holds behaviour when a member is a function, an accessor or a
106
+ // reference to a function; otherwise it is data.
107
+ function holdsBehaviour(sourceCode, object) {
108
+ return object.properties.some((property) => isInlineFunction(property) ||
109
+ (property.type === "Property" && property.kind !== "init") || refersToFunction(sourceCode, property));
110
+ }
111
+
112
+ // The function a factory name refers to in this module, or null.
113
+ function localFunction(sourceCode, identifier) {
114
+ const definition = definitionOf(sourceCode, identifier);
115
+ if (definition?.type === "FunctionName") {
116
+ return definition.node;
117
+ }
118
+ const init = definition?.type === "Variable" ? definition.node.init : null;
119
+ return FUNCTION_TYPES.has(init?.type) ? init : null;
120
+ }
121
+
122
+ // A module function that returns only data: every value it returns is an
123
+ // object literal, frozen or not, without behaviour.
124
+ function returnsOnlyData(sourceCode, fn) {
125
+ const returned = [];
126
+ if (fn.body.type !== "BlockStatement") {
127
+ returned.push(fn.body);
128
+ } else {
129
+ const visit = (node) => {
130
+ if (!node || typeof node.type !== "string" || (node !== fn && FUNCTION_TYPES.has(node.type))) {
131
+ return;
132
+ }
133
+ if (node.type === "ReturnStatement") {
134
+ returned.push(node.argument);
135
+ }
136
+ for (const key of sourceCode.visitorKeys[node.type] || []) {
137
+ [node[key]].flat().forEach(visit);
138
+ }
139
+ };
140
+ visit(fn.body);
141
+ }
142
+ return returned.length > 0 && returned.every((value) => {
143
+ const object = isFreezeCall(value) ? value.arguments[0] : value;
144
+ return object?.type === "ObjectExpression" && !holdsBehaviour(sourceCode, object);
145
+ });
146
+ }
147
+
148
+ export {
149
+ CREATE_NAME,
150
+ FUNCTION_TYPES,
151
+ VUE_STATE_CALLS,
152
+ definedName,
153
+ definitionOf,
154
+ enclosingFunction,
155
+ holdsBehaviour,
156
+ isInlineFunction,
157
+ isVueStateCall,
158
+ localFunction,
159
+ makesVueState,
160
+ returnedFrom,
161
+ returnsOnlyData
162
+ };
@@ -0,0 +1,64 @@
1
+ import { actionCreator } from "./rules/actionCreator.js";
2
+ import { actionExecuteShape } from "./rules/actionExecuteShape.js";
3
+ import { allowCommentForm } from "./rules/allowCommentForm.js";
4
+ import { appErrorCode } from "./rules/appErrorCode.js";
5
+ import { collaboratorProps } from "./rules/collaboratorProps.js";
6
+ import { fixedResultShape } from "./rules/fixedResultShape.js";
7
+ import { frozenFactoryResult } from "./rules/frozenFactoryResult.js";
8
+ import { noOptionalAfterDefault } from "./rules/noOptionalAfterDefault.js";
9
+ import { jsdocDescriptionInside } from "./rules/jsdocDescriptionInside.js";
10
+ import { namedRepeatedType } from "./rules/namedRepeatedType.js";
11
+ import { noDualShape } from "./rules/noDualShape.js";
12
+ import { noDuplicateHelper } from "./rules/noDuplicateHelper.js";
13
+ import { noEnvDefault } from "./rules/noEnvDefault.js";
14
+ import { noFactorySpread } from "./rules/noFactorySpread.js";
15
+ import { noFrozenVueState } from "./rules/noFrozenVueState.js";
16
+ import { noIdempotency } from "./rules/noIdempotency.js";
17
+ import { noModuleRegistry } from "./rules/noModuleRegistry.js";
18
+ import { noModuleState } from "./rules/noModuleState.js";
19
+ import { noPatchedError } from "./rules/noPatchedError.js";
20
+ import { noSlotSetter } from "./rules/noSlotSetter.js";
21
+ import { noTestOnlyExport } from "./rules/noTestOnlyExport.js";
22
+ import { oneLineAction } from "./rules/oneLineAction.js";
23
+ import { onePropertyPerLine } from "./rules/onePropertyPerLine.js";
24
+ import { releasedListeners } from "./rules/releasedListeners.js";
25
+ import { requiredCollaborators } from "./rules/requiredCollaborators.js";
26
+ import { typedDependencies } from "./rules/typedDependencies.js";
27
+ import { typedInject } from "./rules/typedInject.js";
28
+
29
+ // The `jskit` ESLint plugin: the typed-features rules, by name, server and client. Each rule's messages link to the
30
+ // guide section that explains it.
31
+ const jskitPlugin = Object.freeze({
32
+ meta: Object.freeze({ name: "@jskit-ai/config-eslint/jskit" }),
33
+ rules: Object.freeze({
34
+ "typed-dependencies": typedDependencies,
35
+ "required-collaborators": requiredCollaborators,
36
+ "no-slot-setter": noSlotSetter,
37
+ "no-module-registry": noModuleRegistry,
38
+ "one-line-action": oneLineAction,
39
+ "no-duplicate-helper": noDuplicateHelper,
40
+ "allow-comment-form": allowCommentForm,
41
+ "jsdoc-description-inside": jsdocDescriptionInside,
42
+ "no-dual-shape": noDualShape,
43
+ "named-repeated-type": namedRepeatedType,
44
+ "action-execute-shape": actionExecuteShape,
45
+ "no-factory-spread": noFactorySpread,
46
+ "frozen-factory-result": frozenFactoryResult,
47
+ "no-optional-after-default": noOptionalAfterDefault,
48
+ "one-property-per-line": onePropertyPerLine,
49
+ "action-creator": actionCreator,
50
+ "no-idempotency": noIdempotency,
51
+ "app-error-code": appErrorCode,
52
+ "no-patched-error": noPatchedError,
53
+ "no-env-default": noEnvDefault,
54
+ "no-test-only-export": noTestOnlyExport,
55
+ "typed-inject": typedInject,
56
+ "collaborator-props": collaboratorProps,
57
+ "fixed-result-shape": fixedResultShape,
58
+ "released-listeners": releasedListeners,
59
+ "no-module-state": noModuleState,
60
+ "no-frozen-vue-state": noFrozenVueState
61
+ })
62
+ });
63
+
64
+ export { jskitPlugin };