@jskit-ai/config-eslint 0.1.242 → 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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jskit-ai/config-eslint",
3
- "version": "0.1.242",
3
+ "version": "0.1.243",
4
4
  "description": "Shared flat ESLint presets for JSKIT projects.",
5
5
  "type": "module",
6
6
  "files": [
@@ -33,16 +33,31 @@ function isTypedInjection(sourceCode, call) {
33
33
  return hasJsdocBefore(sourceCode, owner, RETURNS_TAG);
34
34
  }
35
35
 
36
+ /**
37
+ * The `required` option of the function holding an inject() call, as in `useRuntime({ required = false } = {})`.
38
+ * @param {any} sourceCode
39
+ * @param {any} call
40
+ */
41
+ function requiredOption(sourceCode, call) {
42
+ const first = sourceCode.getAncestors(call).findLast((ancestor) => FUNCTION_TYPES.has(ancestor.type))?.params[0];
43
+ const pattern = first?.type === "AssignmentPattern" ? first.left : first;
44
+ return pattern?.type === "ObjectPattern"
45
+ ? pattern.properties.find((property) => property.type === "Property" && property.key.name === "required")
46
+ : undefined;
47
+ }
48
+
36
49
  // An injected value is a capability lookup: it takes the owner's exported key,
37
50
  // names its type, and resolves once, either required (`inject(KEY)`) or to the
38
51
  // owner's named default (`inject(KEY, NO_X)`), never to null or {} that is then
39
- // probed. A consumer that cannot depend on the key's owner writes the key in
40
- // place, typed and with a named NO_* default, as a server token is written.
52
+ // probed, and never by a `required` mode that gives one key two outcomes. A
53
+ // consumer that cannot depend on the key's owner writes the key in place, typed
54
+ // and with a named NO_* default, as a server token is written.
41
55
  const typedInject = Object.freeze({
42
56
  meta: ruleMeta("Require inject() to take an exported key, name the injected type and never default to nothing.", {
43
57
  literalKey: "inject({{key}}) names its key in place. Import the owner's exported key constant.",
44
58
  absentDefault: "inject(…, {{fallback}}) resolves to nothing, so every use probes it. Inject it required, or default to the owner's named default.",
45
- untypedInject: "inject({{key}}) has no type. Name it with a JSDoc @type on the binding, a cast, or the accessor's @returns."
59
+ untypedInject: "inject({{key}}) has no type. Name it with a JSDoc @type on the binding, a cast, or the accessor's @returns.",
60
+ requiredMode: "A required option gives inject({{key}}) two outcomes. Resolve it one way; a caller that cannot work without the value refuses where it receives it."
46
61
  }, "injected-values", CLIENT_GUIDE_URL),
47
62
  create(context) {
48
63
  const sourceCode = context.sourceCode;
@@ -69,6 +84,10 @@ const typedInject = Object.freeze({
69
84
  if (!isTypedInjection(sourceCode, node)) {
70
85
  report(node, "untyped-inject", "untypedInject", { key: keyText });
71
86
  }
87
+ const required = requiredOption(sourceCode, node);
88
+ if (required) {
89
+ report(required, "inject-required-mode", "requiredMode", { key: keyText });
90
+ }
72
91
  }
73
92
  };
74
93
  }