@optique/core 1.4.0-dev.2713 → 1.4.0-dev.2716

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.
@@ -2,6 +2,7 @@ const require_internal_annotations = require('./internal/annotations.cjs');
2
2
  const require_mode_dispatch = require('./internal/mode-dispatch.cjs');
3
3
  const require_internal_parser = require('./internal/parser.cjs');
4
4
  const require_annotation_state = require('./annotation-state.cjs');
5
+ const require_empty_input = require('./internal/empty-input.cjs');
5
6
  const require_short_option = require('./short-option.cjs');
6
7
  const require_passthrough = require('./internal/passthrough.cjs');
7
8
  const require_phase2_seed = require('./phase2-seed.cjs');
@@ -104,15 +105,92 @@ function delegateOptionParsing(wrapper, inner, getInnerState) {
104
105
  state: getInnerState(state)
105
106
  }]);
106
107
  }
108
+ /**
109
+ * Declares a parser's static empty-input behavior for usage-group formatting.
110
+ * Facts must hold across runtime annotations and source contexts; omit outcomes
111
+ * that depend on them. These facts do not change parsing or branch eligibility
112
+ * (leadingNames and acceptingAnyToken still determine eligibility).
113
+ *
114
+ * The declaration replaces any prior facts, and an empty object clears them.
115
+ * It is copied and frozen, bound to parse(), complete(), mode and initialState,
116
+ * and not copied by object spread. State getters are bound by identity without
117
+ * invoking them. Declare after finalizing methods and before composing parsers:
118
+ * combinators capture facts and usage at construction. Later closure or in-place
119
+ * state mutations cannot be detected. Initial-state getters must remain cheap,
120
+ * side-effect free and semantically stable.
121
+ *
122
+ * State-shape information remains internal, so a declared successful empty step
123
+ * alone cannot determine whether multiple() retains a repetition item.
124
+ *
125
+ * @param parser The parser to annotate.
126
+ * @param behavior The known outcomes; omitted fields stay unknown.
127
+ * @throws {TypeError} If a value is invalid, afterStep is supplied for a failing
128
+ * step, or the metadata cannot be defined or cleared.
129
+ * @since 1.4.0
130
+ */
131
+ function defineEmptyInputBehavior(parser, behavior) {
132
+ if (behavior.step !== void 0 && behavior.step !== "success" && behavior.step !== "provisional" && behavior.step !== "failure") throw new TypeError("Invalid empty-input step.");
133
+ if (behavior.afterStep !== void 0 && typeof behavior.afterStep !== "boolean" || behavior.fromInitial !== void 0 && typeof behavior.fromInitial !== "boolean") throw new TypeError("Empty-input completion outcomes must be booleans.");
134
+ if (behavior.step === "failure" && behavior.afterStep !== void 0) throw new TypeError("A failing empty-input step has no completion state.");
135
+ const facts = projectEmptyInputBehavior(behavior);
136
+ require_empty_input.defineEmptyInputFacts(parser, Object.keys(facts).length < 1 ? {} : {
137
+ ...facts,
138
+ probe: facts.step === void 0 ? {} : { step: facts.step }
139
+ });
140
+ }
141
+ /**
142
+ * Reads known empty-input outcomes without executing the parser or its state
143
+ * getter. Returns a frozen object; absent or invalidated facts read as unknown.
144
+ * Internal state-shape information is never exposed.
145
+ * @param parser The parser to inspect.
146
+ * @returns The known public facts, with unknown fields omitted.
147
+ * @since 1.4.0
148
+ */
149
+ function getEmptyInputBehavior(parser) {
150
+ return projectEmptyInputBehavior(require_empty_input.getEmptyInputFacts(parser));
151
+ }
152
+ /**
153
+ * Preserves empty-input facts when constructing a transparent parser wrapper.
154
+ * Calling this asserts that the wrapper preserves the three public outcomes.
155
+ * A wrapper that changes completion or empty-step semantics must instead use
156
+ * getEmptyInputBehavior() and defineEmptyInputBehavior() for the valid subset.
157
+ *
158
+ * Internal probe-completion and repetition-state facts are preserved only
159
+ * when parse(),
160
+ * complete(), and the initial-state binding are identical. Spreading a parser
161
+ * with a state getter snapshots its value and loses those facts; preserve
162
+ * its property descriptors or delegate through its prototype to keep the getter.
163
+ * Unknown source facts clear the destination. Finalize both parsers before
164
+ * inheritance and keep them immutable after composing them.
165
+ *
166
+ * @param wrapper The parser receiving the facts.
167
+ * @param inner The parser whose behavior is preserved.
168
+ * @throws {TypeError} If modes differ or metadata cannot be defined or cleared.
169
+ * @since 1.4.0
170
+ */
171
+ function inheritEmptyInputBehavior(wrapper, inner) {
172
+ if (wrapper.mode !== inner.mode) throw new TypeError("Cannot inherit empty-input behavior across parser modes.");
173
+ require_empty_input.inheritEmptyInputFacts(wrapper, inner);
174
+ }
175
+ function projectEmptyInputBehavior(behavior) {
176
+ return Object.freeze({
177
+ ...behavior.step === void 0 ? {} : { step: behavior.step },
178
+ ...behavior.afterStep === void 0 ? {} : { afterStep: behavior.afterStep },
179
+ ...behavior.fromInitial === void 0 ? {} : { fromInitial: behavior.fromInitial }
180
+ });
181
+ }
107
182
 
108
183
  //#endregion
184
+ exports.defineEmptyInputBehavior = defineEmptyInputBehavior;
109
185
  exports.defineTraits = defineTraits;
110
186
  exports.delegateOptionParsing = delegateOptionParsing;
111
187
  exports.delegateSuggestNodes = delegateSuggestNodes;
112
188
  exports.dispatchByMode = require_mode_dispatch.dispatchByMode;
113
189
  exports.extractPhase2SeedKey = require_phase2_seed.extractPhase2SeedKey;
190
+ exports.getEmptyInputBehavior = getEmptyInputBehavior;
114
191
  exports.getTraits = getTraits;
115
192
  exports.inheritAnnotations = require_internal_annotations.inheritAnnotations;
193
+ exports.inheritEmptyInputBehavior = inheritEmptyInputBehavior;
116
194
  exports.inheritOptionScope = inheritOptionScope$1;
117
195
  exports.injectAnnotations = require_internal_annotations.injectAnnotations;
118
196
  exports.isInjectedAnnotationState = require_internal_annotations.isInjectedAnnotationState;
@@ -117,5 +117,80 @@ declare function mapSourceMetadata<M extends Mode, TValue, TState>(parser: Pick<
117
117
  * @since 1.0.11
118
118
  */
119
119
  declare function delegateOptionParsing<TOuterState, TInnerState>(wrapper: Parser<Mode, unknown, TOuterState>, inner: Parser<Mode, unknown, TInnerState>, getInnerState: (state: TOuterState) => TInnerState): void;
120
+ /**
121
+ * Static, context-independent facts about a parser's empty-input behavior.
122
+ * An omitted field means unknown. Declarations are trusted without executing
123
+ * the parser, its sources, or its completion callbacks.
124
+ *
125
+ * @since 1.4.0
126
+ */
127
+ interface EmptyInputBehavior {
128
+ /**
129
+ * The outcome of parse() from the initial state with an empty buffer and
130
+ * options not terminated. "provisional" means success with provisional: true.
131
+ * No tokens can be consumed from the empty buffer, including on failure.
132
+ */
133
+ readonly step?: "success" | "provisional" | "failure";
134
+ /**
135
+ * Whether complete() succeeds in the completion phase on the state left by
136
+ * a successful or provisional empty step. Omit when step is "failure".
137
+ */
138
+ readonly afterStep?: boolean;
139
+ /** Whether complete() succeeds in the completion phase from initialState. */
140
+ readonly fromInitial?: boolean;
141
+ }
142
+ /**
143
+ * Declares a parser's static empty-input behavior for usage-group formatting.
144
+ * Facts must hold across runtime annotations and source contexts; omit outcomes
145
+ * that depend on them. These facts do not change parsing or branch eligibility
146
+ * (leadingNames and acceptingAnyToken still determine eligibility).
147
+ *
148
+ * The declaration replaces any prior facts, and an empty object clears them.
149
+ * It is copied and frozen, bound to parse(), complete(), mode and initialState,
150
+ * and not copied by object spread. State getters are bound by identity without
151
+ * invoking them. Declare after finalizing methods and before composing parsers:
152
+ * combinators capture facts and usage at construction. Later closure or in-place
153
+ * state mutations cannot be detected. Initial-state getters must remain cheap,
154
+ * side-effect free and semantically stable.
155
+ *
156
+ * State-shape information remains internal, so a declared successful empty step
157
+ * alone cannot determine whether multiple() retains a repetition item.
158
+ *
159
+ * @param parser The parser to annotate.
160
+ * @param behavior The known outcomes; omitted fields stay unknown.
161
+ * @throws {TypeError} If a value is invalid, afterStep is supplied for a failing
162
+ * step, or the metadata cannot be defined or cleared.
163
+ * @since 1.4.0
164
+ */
165
+ declare function defineEmptyInputBehavior(parser: Parser<Mode, unknown, unknown>, behavior: EmptyInputBehavior): void;
166
+ /**
167
+ * Reads known empty-input outcomes without executing the parser or its state
168
+ * getter. Returns a frozen object; absent or invalidated facts read as unknown.
169
+ * Internal state-shape information is never exposed.
170
+ * @param parser The parser to inspect.
171
+ * @returns The known public facts, with unknown fields omitted.
172
+ * @since 1.4.0
173
+ */
174
+ declare function getEmptyInputBehavior(parser: Parser<Mode, unknown, unknown>): EmptyInputBehavior;
175
+ /**
176
+ * Preserves empty-input facts when constructing a transparent parser wrapper.
177
+ * Calling this asserts that the wrapper preserves the three public outcomes.
178
+ * A wrapper that changes completion or empty-step semantics must instead use
179
+ * getEmptyInputBehavior() and defineEmptyInputBehavior() for the valid subset.
180
+ *
181
+ * Internal probe-completion and repetition-state facts are preserved only
182
+ * when parse(),
183
+ * complete(), and the initial-state binding are identical. Spreading a parser
184
+ * with a state getter snapshots its value and loses those facts; preserve
185
+ * its property descriptors or delegate through its prototype to keep the getter.
186
+ * Unknown source facts clear the destination. Finalize both parsers before
187
+ * inheritance and keep them immutable after composing them.
188
+ *
189
+ * @param wrapper The parser receiving the facts.
190
+ * @param inner The parser whose behavior is preserved.
191
+ * @throws {TypeError} If modes differ or metadata cannot be defined or cleared.
192
+ * @since 1.4.0
193
+ */
194
+ declare function inheritEmptyInputBehavior(wrapper: Parser<Mode, unknown, unknown>, inner: Parser<Mode, unknown, unknown>): void;
120
195
  //#endregion
121
- export { ParserSourceMetadata, ParserTraits, SuggestNode, defineTraits, delegateOptionParsing, delegateSuggestNodes, dispatchByMode, extractPhase2SeedKey, getTraits, inheritAnnotations, inheritOptionScope, injectAnnotations, isInjectedAnnotationState, mapModeValue, mapSourceMetadata, unwrapInjectedAnnotationState, withAnnotationView, wrapForMode };
196
+ export { EmptyInputBehavior, ParserSourceMetadata, ParserTraits, SuggestNode, defineEmptyInputBehavior, defineTraits, delegateOptionParsing, delegateSuggestNodes, dispatchByMode, extractPhase2SeedKey, getEmptyInputBehavior, getTraits, inheritAnnotations, inheritEmptyInputBehavior, inheritOptionScope, injectAnnotations, isInjectedAnnotationState, mapModeValue, mapSourceMetadata, unwrapInjectedAnnotationState, withAnnotationView, wrapForMode };
@@ -117,5 +117,80 @@ declare function mapSourceMetadata<M extends Mode, TValue, TState>(parser: Pick<
117
117
  * @since 1.0.11
118
118
  */
119
119
  declare function delegateOptionParsing<TOuterState, TInnerState>(wrapper: Parser<Mode, unknown, TOuterState>, inner: Parser<Mode, unknown, TInnerState>, getInnerState: (state: TOuterState) => TInnerState): void;
120
+ /**
121
+ * Static, context-independent facts about a parser's empty-input behavior.
122
+ * An omitted field means unknown. Declarations are trusted without executing
123
+ * the parser, its sources, or its completion callbacks.
124
+ *
125
+ * @since 1.4.0
126
+ */
127
+ interface EmptyInputBehavior {
128
+ /**
129
+ * The outcome of parse() from the initial state with an empty buffer and
130
+ * options not terminated. "provisional" means success with provisional: true.
131
+ * No tokens can be consumed from the empty buffer, including on failure.
132
+ */
133
+ readonly step?: "success" | "provisional" | "failure";
134
+ /**
135
+ * Whether complete() succeeds in the completion phase on the state left by
136
+ * a successful or provisional empty step. Omit when step is "failure".
137
+ */
138
+ readonly afterStep?: boolean;
139
+ /** Whether complete() succeeds in the completion phase from initialState. */
140
+ readonly fromInitial?: boolean;
141
+ }
142
+ /**
143
+ * Declares a parser's static empty-input behavior for usage-group formatting.
144
+ * Facts must hold across runtime annotations and source contexts; omit outcomes
145
+ * that depend on them. These facts do not change parsing or branch eligibility
146
+ * (leadingNames and acceptingAnyToken still determine eligibility).
147
+ *
148
+ * The declaration replaces any prior facts, and an empty object clears them.
149
+ * It is copied and frozen, bound to parse(), complete(), mode and initialState,
150
+ * and not copied by object spread. State getters are bound by identity without
151
+ * invoking them. Declare after finalizing methods and before composing parsers:
152
+ * combinators capture facts and usage at construction. Later closure or in-place
153
+ * state mutations cannot be detected. Initial-state getters must remain cheap,
154
+ * side-effect free and semantically stable.
155
+ *
156
+ * State-shape information remains internal, so a declared successful empty step
157
+ * alone cannot determine whether multiple() retains a repetition item.
158
+ *
159
+ * @param parser The parser to annotate.
160
+ * @param behavior The known outcomes; omitted fields stay unknown.
161
+ * @throws {TypeError} If a value is invalid, afterStep is supplied for a failing
162
+ * step, or the metadata cannot be defined or cleared.
163
+ * @since 1.4.0
164
+ */
165
+ declare function defineEmptyInputBehavior(parser: Parser<Mode, unknown, unknown>, behavior: EmptyInputBehavior): void;
166
+ /**
167
+ * Reads known empty-input outcomes without executing the parser or its state
168
+ * getter. Returns a frozen object; absent or invalidated facts read as unknown.
169
+ * Internal state-shape information is never exposed.
170
+ * @param parser The parser to inspect.
171
+ * @returns The known public facts, with unknown fields omitted.
172
+ * @since 1.4.0
173
+ */
174
+ declare function getEmptyInputBehavior(parser: Parser<Mode, unknown, unknown>): EmptyInputBehavior;
175
+ /**
176
+ * Preserves empty-input facts when constructing a transparent parser wrapper.
177
+ * Calling this asserts that the wrapper preserves the three public outcomes.
178
+ * A wrapper that changes completion or empty-step semantics must instead use
179
+ * getEmptyInputBehavior() and defineEmptyInputBehavior() for the valid subset.
180
+ *
181
+ * Internal probe-completion and repetition-state facts are preserved only
182
+ * when parse(),
183
+ * complete(), and the initial-state binding are identical. Spreading a parser
184
+ * with a state getter snapshots its value and loses those facts; preserve
185
+ * its property descriptors or delegate through its prototype to keep the getter.
186
+ * Unknown source facts clear the destination. Finalize both parsers before
187
+ * inheritance and keep them immutable after composing them.
188
+ *
189
+ * @param wrapper The parser receiving the facts.
190
+ * @param inner The parser whose behavior is preserved.
191
+ * @throws {TypeError} If modes differ or metadata cannot be defined or cleared.
192
+ * @since 1.4.0
193
+ */
194
+ declare function inheritEmptyInputBehavior(wrapper: Parser<Mode, unknown, unknown>, inner: Parser<Mode, unknown, unknown>): void;
120
195
  //#endregion
121
- export { ParserSourceMetadata, ParserTraits, SuggestNode, defineTraits, delegateOptionParsing, delegateSuggestNodes, dispatchByMode, extractPhase2SeedKey, getTraits, inheritAnnotations, inheritOptionScope, injectAnnotations, isInjectedAnnotationState, mapModeValue, mapSourceMetadata, unwrapInjectedAnnotationState, withAnnotationView, wrapForMode };
196
+ export { EmptyInputBehavior, ParserSourceMetadata, ParserTraits, SuggestNode, defineEmptyInputBehavior, defineTraits, delegateOptionParsing, delegateSuggestNodes, dispatchByMode, extractPhase2SeedKey, getEmptyInputBehavior, getTraits, inheritAnnotations, inheritEmptyInputBehavior, inheritOptionScope, injectAnnotations, isInjectedAnnotationState, mapModeValue, mapSourceMetadata, unwrapInjectedAnnotationState, withAnnotationView, wrapForMode };
package/dist/extension.js CHANGED
@@ -2,6 +2,7 @@ import { inheritAnnotations, injectAnnotations, isInjectedAnnotationState, unwra
2
2
  import { dispatchByMode, mapModeValue, wrapForMode } from "./internal/mode-dispatch.js";
3
3
  import { annotationWrapperRequiresSourceBindingKey, composeWrappedSourceMetadata, defineInheritedAnnotationParser, getDelegatingSuggestRuntimeNodes, inheritParentAnnotationsKey, unmatchedNonCliDependencySourceStateMarker } from "./internal/parser.js";
4
4
  import { withAnnotationView } from "./annotation-state.js";
5
+ import { defineEmptyInputFacts, getEmptyInputFacts, inheritEmptyInputFacts } from "./internal/empty-input.js";
5
6
  import { inheritOptionScope } from "./short-option.js";
6
7
  import { defineReachableChildren } from "./internal/passthrough.js";
7
8
  import { extractPhase2SeedKey } from "./phase2-seed.js";
@@ -104,6 +105,80 @@ function delegateOptionParsing(wrapper, inner, getInnerState) {
104
105
  state: getInnerState(state)
105
106
  }]);
106
107
  }
108
+ /**
109
+ * Declares a parser's static empty-input behavior for usage-group formatting.
110
+ * Facts must hold across runtime annotations and source contexts; omit outcomes
111
+ * that depend on them. These facts do not change parsing or branch eligibility
112
+ * (leadingNames and acceptingAnyToken still determine eligibility).
113
+ *
114
+ * The declaration replaces any prior facts, and an empty object clears them.
115
+ * It is copied and frozen, bound to parse(), complete(), mode and initialState,
116
+ * and not copied by object spread. State getters are bound by identity without
117
+ * invoking them. Declare after finalizing methods and before composing parsers:
118
+ * combinators capture facts and usage at construction. Later closure or in-place
119
+ * state mutations cannot be detected. Initial-state getters must remain cheap,
120
+ * side-effect free and semantically stable.
121
+ *
122
+ * State-shape information remains internal, so a declared successful empty step
123
+ * alone cannot determine whether multiple() retains a repetition item.
124
+ *
125
+ * @param parser The parser to annotate.
126
+ * @param behavior The known outcomes; omitted fields stay unknown.
127
+ * @throws {TypeError} If a value is invalid, afterStep is supplied for a failing
128
+ * step, or the metadata cannot be defined or cleared.
129
+ * @since 1.4.0
130
+ */
131
+ function defineEmptyInputBehavior(parser, behavior) {
132
+ if (behavior.step !== void 0 && behavior.step !== "success" && behavior.step !== "provisional" && behavior.step !== "failure") throw new TypeError("Invalid empty-input step.");
133
+ if (behavior.afterStep !== void 0 && typeof behavior.afterStep !== "boolean" || behavior.fromInitial !== void 0 && typeof behavior.fromInitial !== "boolean") throw new TypeError("Empty-input completion outcomes must be booleans.");
134
+ if (behavior.step === "failure" && behavior.afterStep !== void 0) throw new TypeError("A failing empty-input step has no completion state.");
135
+ const facts = projectEmptyInputBehavior(behavior);
136
+ defineEmptyInputFacts(parser, Object.keys(facts).length < 1 ? {} : {
137
+ ...facts,
138
+ probe: facts.step === void 0 ? {} : { step: facts.step }
139
+ });
140
+ }
141
+ /**
142
+ * Reads known empty-input outcomes without executing the parser or its state
143
+ * getter. Returns a frozen object; absent or invalidated facts read as unknown.
144
+ * Internal state-shape information is never exposed.
145
+ * @param parser The parser to inspect.
146
+ * @returns The known public facts, with unknown fields omitted.
147
+ * @since 1.4.0
148
+ */
149
+ function getEmptyInputBehavior(parser) {
150
+ return projectEmptyInputBehavior(getEmptyInputFacts(parser));
151
+ }
152
+ /**
153
+ * Preserves empty-input facts when constructing a transparent parser wrapper.
154
+ * Calling this asserts that the wrapper preserves the three public outcomes.
155
+ * A wrapper that changes completion or empty-step semantics must instead use
156
+ * getEmptyInputBehavior() and defineEmptyInputBehavior() for the valid subset.
157
+ *
158
+ * Internal probe-completion and repetition-state facts are preserved only
159
+ * when parse(),
160
+ * complete(), and the initial-state binding are identical. Spreading a parser
161
+ * with a state getter snapshots its value and loses those facts; preserve
162
+ * its property descriptors or delegate through its prototype to keep the getter.
163
+ * Unknown source facts clear the destination. Finalize both parsers before
164
+ * inheritance and keep them immutable after composing them.
165
+ *
166
+ * @param wrapper The parser receiving the facts.
167
+ * @param inner The parser whose behavior is preserved.
168
+ * @throws {TypeError} If modes differ or metadata cannot be defined or cleared.
169
+ * @since 1.4.0
170
+ */
171
+ function inheritEmptyInputBehavior(wrapper, inner) {
172
+ if (wrapper.mode !== inner.mode) throw new TypeError("Cannot inherit empty-input behavior across parser modes.");
173
+ inheritEmptyInputFacts(wrapper, inner);
174
+ }
175
+ function projectEmptyInputBehavior(behavior) {
176
+ return Object.freeze({
177
+ ...behavior.step === void 0 ? {} : { step: behavior.step },
178
+ ...behavior.afterStep === void 0 ? {} : { afterStep: behavior.afterStep },
179
+ ...behavior.fromInitial === void 0 ? {} : { fromInitial: behavior.fromInitial }
180
+ });
181
+ }
107
182
 
108
183
  //#endregion
109
- export { defineTraits, delegateOptionParsing, delegateSuggestNodes, dispatchByMode, extractPhase2SeedKey, getTraits, inheritAnnotations, inheritOptionScope$1 as inheritOptionScope, injectAnnotations, isInjectedAnnotationState, mapModeValue, mapSourceMetadata, unwrapInjectedAnnotationState, withAnnotationView, wrapForMode };
184
+ export { defineEmptyInputBehavior, defineTraits, delegateOptionParsing, delegateSuggestNodes, dispatchByMode, extractPhase2SeedKey, getEmptyInputBehavior, getTraits, inheritAnnotations, inheritEmptyInputBehavior, inheritOptionScope$1 as inheritOptionScope, injectAnnotations, isInjectedAnnotationState, mapModeValue, mapSourceMetadata, unwrapInjectedAnnotationState, withAnnotationView, wrapForMode };
@@ -17,24 +17,50 @@ function resolveEmptyStepState(next, initialState) {
17
17
  return "unchanged" in next ? initialState : next.state(initialState);
18
18
  }
19
19
  const emptyInputFactsKey = Symbol("emptyInputFacts");
20
+ function initialStateBinding(parser) {
21
+ let target = parser;
22
+ while (target != null) {
23
+ const descriptor = Object.getOwnPropertyDescriptor(target, "initialState");
24
+ if (descriptor != null) return "value" in descriptor ? {
25
+ kind: "data",
26
+ value: descriptor.value
27
+ } : {
28
+ kind: "accessor",
29
+ get: descriptor.get
30
+ };
31
+ target = Object.getPrototypeOf(target);
32
+ }
33
+ return {
34
+ kind: "data",
35
+ value: void 0
36
+ };
37
+ }
38
+ function sameInitialState(a, b) {
39
+ return a.kind === "data" && b.kind === "data" ? Object.is(a.value, b.value) : a.kind === "accessor" && b.kind === "accessor" && a.get === b.get;
40
+ }
20
41
  const UNKNOWN = Object.freeze({});
21
42
  /**
22
43
  * Attaches empty-input facts to a parser. The facts are stored as a
23
44
  * non-enumerable property, so object spread does not copy them, and they
24
45
  * are bound to the parser's current `parse`, `complete`, and
25
- * `initialState`, so replacing any of those invalidates them.
46
+ * `initialState`, and mode, so replacing any of those invalidates them.
47
+ * State accessors are bound by getter identity without invoking them.
26
48
  * @param parser The parser to annotate.
27
49
  * @param facts The facts to attach.
28
50
  * @internal
29
51
  */
30
52
  function defineEmptyInputFacts(parser, facts) {
31
- if (Object.keys(facts).length < 1) return;
53
+ if (Object.keys(facts).length < 1) {
54
+ if (!Reflect.deleteProperty(parser, emptyInputFactsKey)) throw new TypeError("Cannot clear empty-input facts on this parser.");
55
+ return;
56
+ }
32
57
  Object.defineProperty(parser, emptyInputFactsKey, {
33
58
  value: {
34
- facts,
59
+ facts: Object.freeze({ ...facts }),
35
60
  parse: parser.parse,
36
61
  complete: parser.complete,
37
- ...Object.getOwnPropertyDescriptor(parser, "initialState")?.get == null ? { initialState: { value: parser.initialState } } : {}
62
+ mode: parser.mode,
63
+ initialState: initialStateBinding(parser)
38
64
  },
39
65
  configurable: true,
40
66
  enumerable: false,
@@ -49,11 +75,29 @@ function defineEmptyInputFacts(parser, facts) {
49
75
  * @internal
50
76
  */
51
77
  function getEmptyInputFacts(parser) {
78
+ if (!Object.hasOwn(parser, emptyInputFactsKey)) return UNKNOWN;
52
79
  const stored = parser[emptyInputFactsKey];
53
- if (stored == null || stored.parse !== parser.parse || stored.complete !== parser.complete || stored.initialState != null && stored.initialState.value !== parser.initialState) return UNKNOWN;
80
+ if (stored == null || stored.parse !== parser.parse || stored.complete !== parser.complete || stored.mode !== parser.mode || !sameInitialState(stored.initialState, initialStateBinding(parser))) return UNKNOWN;
54
81
  return stored.facts;
55
82
  }
56
83
  /**
84
+ * Copies facts for an explicitly transparent wrapper, preserving internal
85
+ * state information only when its execution members match the source.
86
+ * @param wrapper The receiving parser.
87
+ * @param inner The wrapped parser.
88
+ * @internal
89
+ */
90
+ function inheritEmptyInputFacts(wrapper, inner) {
91
+ const facts = getEmptyInputFacts(inner);
92
+ const identical = wrapper.parse === inner.parse && wrapper.complete === inner.complete && sameInitialState(initialStateBinding(wrapper), initialStateBinding(inner));
93
+ defineEmptyInputFacts(wrapper, identical ? facts : {
94
+ ...Object.keys(facts).length < 1 ? {} : { probe: facts.step === void 0 ? {} : { step: facts.step } },
95
+ ...facts.step === void 0 ? {} : { step: facts.step },
96
+ ...facts.afterStep === void 0 ? {} : { afterStep: facts.afterStep },
97
+ ...facts.fromInitial === void 0 ? {} : { fromInitial: facts.fromInitial }
98
+ });
99
+ }
100
+ /**
57
101
  * Returns whether `parse(parser, [])` succeeds, when that is known from the
58
102
  * facts.
59
103
  * @param facts The parser's facts.
@@ -109,6 +153,13 @@ function wrapInFreshArray(child, childInitialState) {
109
153
  * @internal
110
154
  */
111
155
  function optionalLikeFacts(child, childInitialState, fromInitial) {
156
+ const facts = computeOptionalLikeFacts(child, childInitialState, fromInitial);
157
+ return child.probe !== void 0 ? {
158
+ ...facts,
159
+ probe: computeOptionalLikeFacts(probeFacts(child), childInitialState, fromInitial)
160
+ } : facts;
161
+ }
162
+ function computeOptionalLikeFacts(child, childInitialState, fromInitial) {
112
163
  if (child.step === "failure") return {
113
164
  step: "success",
114
165
  next: UNCHANGED,
@@ -134,6 +185,13 @@ function optionalLikeFacts(child, childInitialState, fromInitial) {
134
185
  * @internal
135
186
  */
136
187
  function multipleFacts(child, retainsItem, childInitialState, min) {
188
+ const facts = computeMultipleFacts(child, retainsItem, childInitialState, min);
189
+ return child.probe !== void 0 ? {
190
+ ...facts,
191
+ probe: computeMultipleFacts(probeFacts(child), retainsItem, childInitialState, min)
192
+ } : facts;
193
+ }
194
+ function computeMultipleFacts(child, retainsItem, childInitialState, min) {
137
195
  const fromInitial = min < 1;
138
196
  if (child.step === "failure") return min < 1 ? {
139
197
  step: "success",
@@ -200,6 +258,16 @@ function exclusiveNextState(index) {
200
258
  * @internal
201
259
  */
202
260
  function orFacts(branches) {
261
+ const facts = computeOrFacts(branches);
262
+ return branches.some((c) => c.facts.probe !== void 0) ? {
263
+ ...facts,
264
+ probe: computeOrFacts(branches.map((b) => ({
265
+ ...b,
266
+ facts: probeFacts(b.facts)
267
+ })))
268
+ } : facts;
269
+ }
270
+ function computeOrFacts(branches) {
203
271
  const candidate = selectZeroInputCandidate(branches);
204
272
  if (candidate === void 0) return {};
205
273
  const fromInitial = exclusiveFromInitial(branches);
@@ -223,6 +291,16 @@ function orFacts(branches) {
223
291
  * @internal
224
292
  */
225
293
  function longestMatchFacts(branches) {
294
+ const facts = computeLongestMatchFacts(branches);
295
+ return branches.some((c) => c.facts.probe !== void 0) ? {
296
+ ...facts,
297
+ probe: computeLongestMatchFacts(branches.map((b) => ({
298
+ ...b,
299
+ facts: probeFacts(b.facts)
300
+ })))
301
+ } : facts;
302
+ }
303
+ function computeLongestMatchFacts(branches) {
226
304
  const fromInitial = exclusiveFromInitial(branches);
227
305
  let best = null;
228
306
  for (let i = 0; i < branches.length; i++) {
@@ -240,7 +318,7 @@ function longestMatchFacts(branches) {
240
318
  fromInitial
241
319
  };
242
320
  return {
243
- step: best.facts.step,
321
+ step: "success",
244
322
  next: exclusiveNextState(best.index),
245
323
  afterStep: best.facts.afterStep,
246
324
  fromInitial
@@ -263,6 +341,16 @@ function all(values) {
263
341
  * @internal
264
342
  */
265
343
  function tupleFacts(children) {
344
+ const facts = computeTupleFacts(children);
345
+ return children.some((c) => c.facts.probe !== void 0) ? {
346
+ ...facts,
347
+ probe: computeTupleFacts(children.map((c) => ({
348
+ ...c,
349
+ facts: probeFacts(c.facts)
350
+ })))
351
+ } : facts;
352
+ }
353
+ function computeTupleFacts(children) {
266
354
  const fromInitial = all(children.map((c) => c.facts.fromInitial));
267
355
  if (children.some((c) => c.facts.step == null)) return {
268
356
  step: "success",
@@ -308,6 +396,20 @@ function objectNextState(changed) {
308
396
  * @internal
309
397
  */
310
398
  function objectFacts(children, keys) {
399
+ const completion = computeObjectFacts(children, keys);
400
+ if (!children.some((c) => c.facts.probe !== void 0)) return completion;
401
+ const probe = computeObjectFacts(children.map((c) => ({
402
+ ...c,
403
+ facts: probeFacts(c.facts)
404
+ })), keys);
405
+ return {
406
+ ...probe,
407
+ fromInitial: completion.fromInitial,
408
+ afterStep: probe.step === "success" ? completion.afterStep : void 0,
409
+ probe
410
+ };
411
+ }
412
+ function computeObjectFacts(children, keys) {
311
413
  const fromInitial = all(children.map((c) => c.facts.fromInitial));
312
414
  const outcomes = [];
313
415
  const changed = [];
@@ -351,6 +453,9 @@ function withEmptyInputFacts(parser, facts) {
351
453
  defineEmptyInputFacts(parser, facts);
352
454
  return parser;
353
455
  }
456
+ function probeFacts(facts) {
457
+ return facts.probe ?? facts;
458
+ }
354
459
 
355
460
  //#endregion
356
461
  exports.UNCHANGED = UNCHANGED;
@@ -358,6 +463,7 @@ exports.acceptsEmptyInput = acceptsEmptyInput;
358
463
  exports.defineEmptyInputFacts = defineEmptyInputFacts;
359
464
  exports.failingStepFacts = failingStepFacts;
360
465
  exports.getEmptyInputFacts = getEmptyInputFacts;
466
+ exports.inheritEmptyInputFacts = inheritEmptyInputFacts;
361
467
  exports.longestMatchFacts = longestMatchFacts;
362
468
  exports.multipleFacts = multipleFacts;
363
469
  exports.objectFacts = objectFacts;
@@ -16,24 +16,50 @@ function resolveEmptyStepState(next, initialState) {
16
16
  return "unchanged" in next ? initialState : next.state(initialState);
17
17
  }
18
18
  const emptyInputFactsKey = Symbol("emptyInputFacts");
19
+ function initialStateBinding(parser) {
20
+ let target = parser;
21
+ while (target != null) {
22
+ const descriptor = Object.getOwnPropertyDescriptor(target, "initialState");
23
+ if (descriptor != null) return "value" in descriptor ? {
24
+ kind: "data",
25
+ value: descriptor.value
26
+ } : {
27
+ kind: "accessor",
28
+ get: descriptor.get
29
+ };
30
+ target = Object.getPrototypeOf(target);
31
+ }
32
+ return {
33
+ kind: "data",
34
+ value: void 0
35
+ };
36
+ }
37
+ function sameInitialState(a, b) {
38
+ return a.kind === "data" && b.kind === "data" ? Object.is(a.value, b.value) : a.kind === "accessor" && b.kind === "accessor" && a.get === b.get;
39
+ }
19
40
  const UNKNOWN = Object.freeze({});
20
41
  /**
21
42
  * Attaches empty-input facts to a parser. The facts are stored as a
22
43
  * non-enumerable property, so object spread does not copy them, and they
23
44
  * are bound to the parser's current `parse`, `complete`, and
24
- * `initialState`, so replacing any of those invalidates them.
45
+ * `initialState`, and mode, so replacing any of those invalidates them.
46
+ * State accessors are bound by getter identity without invoking them.
25
47
  * @param parser The parser to annotate.
26
48
  * @param facts The facts to attach.
27
49
  * @internal
28
50
  */
29
51
  function defineEmptyInputFacts(parser, facts) {
30
- if (Object.keys(facts).length < 1) return;
52
+ if (Object.keys(facts).length < 1) {
53
+ if (!Reflect.deleteProperty(parser, emptyInputFactsKey)) throw new TypeError("Cannot clear empty-input facts on this parser.");
54
+ return;
55
+ }
31
56
  Object.defineProperty(parser, emptyInputFactsKey, {
32
57
  value: {
33
- facts,
58
+ facts: Object.freeze({ ...facts }),
34
59
  parse: parser.parse,
35
60
  complete: parser.complete,
36
- ...Object.getOwnPropertyDescriptor(parser, "initialState")?.get == null ? { initialState: { value: parser.initialState } } : {}
61
+ mode: parser.mode,
62
+ initialState: initialStateBinding(parser)
37
63
  },
38
64
  configurable: true,
39
65
  enumerable: false,
@@ -48,11 +74,29 @@ function defineEmptyInputFacts(parser, facts) {
48
74
  * @internal
49
75
  */
50
76
  function getEmptyInputFacts(parser) {
77
+ if (!Object.hasOwn(parser, emptyInputFactsKey)) return UNKNOWN;
51
78
  const stored = parser[emptyInputFactsKey];
52
- if (stored == null || stored.parse !== parser.parse || stored.complete !== parser.complete || stored.initialState != null && stored.initialState.value !== parser.initialState) return UNKNOWN;
79
+ if (stored == null || stored.parse !== parser.parse || stored.complete !== parser.complete || stored.mode !== parser.mode || !sameInitialState(stored.initialState, initialStateBinding(parser))) return UNKNOWN;
53
80
  return stored.facts;
54
81
  }
55
82
  /**
83
+ * Copies facts for an explicitly transparent wrapper, preserving internal
84
+ * state information only when its execution members match the source.
85
+ * @param wrapper The receiving parser.
86
+ * @param inner The wrapped parser.
87
+ * @internal
88
+ */
89
+ function inheritEmptyInputFacts(wrapper, inner) {
90
+ const facts = getEmptyInputFacts(inner);
91
+ const identical = wrapper.parse === inner.parse && wrapper.complete === inner.complete && sameInitialState(initialStateBinding(wrapper), initialStateBinding(inner));
92
+ defineEmptyInputFacts(wrapper, identical ? facts : {
93
+ ...Object.keys(facts).length < 1 ? {} : { probe: facts.step === void 0 ? {} : { step: facts.step } },
94
+ ...facts.step === void 0 ? {} : { step: facts.step },
95
+ ...facts.afterStep === void 0 ? {} : { afterStep: facts.afterStep },
96
+ ...facts.fromInitial === void 0 ? {} : { fromInitial: facts.fromInitial }
97
+ });
98
+ }
99
+ /**
56
100
  * Returns whether `parse(parser, [])` succeeds, when that is known from the
57
101
  * facts.
58
102
  * @param facts The parser's facts.
@@ -108,6 +152,13 @@ function wrapInFreshArray(child, childInitialState) {
108
152
  * @internal
109
153
  */
110
154
  function optionalLikeFacts(child, childInitialState, fromInitial) {
155
+ const facts = computeOptionalLikeFacts(child, childInitialState, fromInitial);
156
+ return child.probe !== void 0 ? {
157
+ ...facts,
158
+ probe: computeOptionalLikeFacts(probeFacts(child), childInitialState, fromInitial)
159
+ } : facts;
160
+ }
161
+ function computeOptionalLikeFacts(child, childInitialState, fromInitial) {
111
162
  if (child.step === "failure") return {
112
163
  step: "success",
113
164
  next: UNCHANGED,
@@ -133,6 +184,13 @@ function optionalLikeFacts(child, childInitialState, fromInitial) {
133
184
  * @internal
134
185
  */
135
186
  function multipleFacts(child, retainsItem, childInitialState, min) {
187
+ const facts = computeMultipleFacts(child, retainsItem, childInitialState, min);
188
+ return child.probe !== void 0 ? {
189
+ ...facts,
190
+ probe: computeMultipleFacts(probeFacts(child), retainsItem, childInitialState, min)
191
+ } : facts;
192
+ }
193
+ function computeMultipleFacts(child, retainsItem, childInitialState, min) {
136
194
  const fromInitial = min < 1;
137
195
  if (child.step === "failure") return min < 1 ? {
138
196
  step: "success",
@@ -199,6 +257,16 @@ function exclusiveNextState(index) {
199
257
  * @internal
200
258
  */
201
259
  function orFacts(branches) {
260
+ const facts = computeOrFacts(branches);
261
+ return branches.some((c) => c.facts.probe !== void 0) ? {
262
+ ...facts,
263
+ probe: computeOrFacts(branches.map((b) => ({
264
+ ...b,
265
+ facts: probeFacts(b.facts)
266
+ })))
267
+ } : facts;
268
+ }
269
+ function computeOrFacts(branches) {
202
270
  const candidate = selectZeroInputCandidate(branches);
203
271
  if (candidate === void 0) return {};
204
272
  const fromInitial = exclusiveFromInitial(branches);
@@ -222,6 +290,16 @@ function orFacts(branches) {
222
290
  * @internal
223
291
  */
224
292
  function longestMatchFacts(branches) {
293
+ const facts = computeLongestMatchFacts(branches);
294
+ return branches.some((c) => c.facts.probe !== void 0) ? {
295
+ ...facts,
296
+ probe: computeLongestMatchFacts(branches.map((b) => ({
297
+ ...b,
298
+ facts: probeFacts(b.facts)
299
+ })))
300
+ } : facts;
301
+ }
302
+ function computeLongestMatchFacts(branches) {
225
303
  const fromInitial = exclusiveFromInitial(branches);
226
304
  let best = null;
227
305
  for (let i = 0; i < branches.length; i++) {
@@ -239,7 +317,7 @@ function longestMatchFacts(branches) {
239
317
  fromInitial
240
318
  };
241
319
  return {
242
- step: best.facts.step,
320
+ step: "success",
243
321
  next: exclusiveNextState(best.index),
244
322
  afterStep: best.facts.afterStep,
245
323
  fromInitial
@@ -262,6 +340,16 @@ function all(values) {
262
340
  * @internal
263
341
  */
264
342
  function tupleFacts(children) {
343
+ const facts = computeTupleFacts(children);
344
+ return children.some((c) => c.facts.probe !== void 0) ? {
345
+ ...facts,
346
+ probe: computeTupleFacts(children.map((c) => ({
347
+ ...c,
348
+ facts: probeFacts(c.facts)
349
+ })))
350
+ } : facts;
351
+ }
352
+ function computeTupleFacts(children) {
265
353
  const fromInitial = all(children.map((c) => c.facts.fromInitial));
266
354
  if (children.some((c) => c.facts.step == null)) return {
267
355
  step: "success",
@@ -307,6 +395,20 @@ function objectNextState(changed) {
307
395
  * @internal
308
396
  */
309
397
  function objectFacts(children, keys) {
398
+ const completion = computeObjectFacts(children, keys);
399
+ if (!children.some((c) => c.facts.probe !== void 0)) return completion;
400
+ const probe = computeObjectFacts(children.map((c) => ({
401
+ ...c,
402
+ facts: probeFacts(c.facts)
403
+ })), keys);
404
+ return {
405
+ ...probe,
406
+ fromInitial: completion.fromInitial,
407
+ afterStep: probe.step === "success" ? completion.afterStep : void 0,
408
+ probe
409
+ };
410
+ }
411
+ function computeObjectFacts(children, keys) {
310
412
  const fromInitial = all(children.map((c) => c.facts.fromInitial));
311
413
  const outcomes = [];
312
414
  const changed = [];
@@ -350,6 +452,9 @@ function withEmptyInputFacts(parser, facts) {
350
452
  defineEmptyInputFacts(parser, facts);
351
453
  return parser;
352
454
  }
455
+ function probeFacts(facts) {
456
+ return facts.probe ?? facts;
457
+ }
353
458
 
354
459
  //#endregion
355
- export { UNCHANGED, acceptsEmptyInput, defineEmptyInputFacts, failingStepFacts, getEmptyInputFacts, longestMatchFacts, multipleFacts, objectFacts, optionalLikeFacts, orFacts, resolveEmptyStepState, tupleFacts, withEmptyInputFacts };
460
+ export { UNCHANGED, acceptsEmptyInput, defineEmptyInputFacts, failingStepFacts, getEmptyInputFacts, inheritEmptyInputFacts, longestMatchFacts, multipleFacts, objectFacts, optionalLikeFacts, orFacts, resolveEmptyStepState, tupleFacts, withEmptyInputFacts };
@@ -945,6 +945,12 @@ function map(parser, transform) {
945
945
  require_dependency_runtime.defineForwardedEffectfulSchedulingNodes(mappedParser, parser);
946
946
  const innerFacts = require_empty_input.getEmptyInputFacts(parser);
947
947
  require_empty_input.defineEmptyInputFacts(mappedParser, {
948
+ ...innerFacts.probe == null ? {} : { probe: {
949
+ step: innerFacts.probe.step,
950
+ next: innerFacts.probe.next,
951
+ ...innerFacts.probe.afterStep === false ? { afterStep: false } : {},
952
+ ...innerFacts.probe.fromInitial === false ? { fromInitial: false } : {}
953
+ } },
948
954
  ...innerFacts.step == null ? {} : { step: innerFacts.step },
949
955
  ...innerFacts.next == null ? {} : { next: innerFacts.next },
950
956
  ...innerFacts.afterStep === false ? { afterStep: false } : {},
@@ -1928,6 +1934,10 @@ function nonEmpty(parser) {
1928
1934
  const scopedNonEmptyParser = require_short_option.scopeParser(nonEmptyParser, require_short_option.combinedOptionScope([parser]));
1929
1935
  const innerFacts = require_empty_input.getEmptyInputFacts(parser);
1930
1936
  require_empty_input.defineEmptyInputFacts(scopedNonEmptyParser, {
1937
+ ...innerFacts.probe == null ? {} : { probe: {
1938
+ step: "failure",
1939
+ fromInitial: innerFacts.probe.fromInitial
1940
+ } },
1931
1941
  step: "failure",
1932
1942
  ...innerFacts.fromInitial == null ? {} : { fromInitial: innerFacts.fromInitial }
1933
1943
  });
package/dist/modifiers.js CHANGED
@@ -945,6 +945,12 @@ function map(parser, transform) {
945
945
  defineForwardedEffectfulSchedulingNodes(mappedParser, parser);
946
946
  const innerFacts = getEmptyInputFacts(parser);
947
947
  defineEmptyInputFacts(mappedParser, {
948
+ ...innerFacts.probe == null ? {} : { probe: {
949
+ step: innerFacts.probe.step,
950
+ next: innerFacts.probe.next,
951
+ ...innerFacts.probe.afterStep === false ? { afterStep: false } : {},
952
+ ...innerFacts.probe.fromInitial === false ? { fromInitial: false } : {}
953
+ } },
948
954
  ...innerFacts.step == null ? {} : { step: innerFacts.step },
949
955
  ...innerFacts.next == null ? {} : { next: innerFacts.next },
950
956
  ...innerFacts.afterStep === false ? { afterStep: false } : {},
@@ -1928,6 +1934,10 @@ function nonEmpty(parser) {
1928
1934
  const scopedNonEmptyParser = scopeParser(nonEmptyParser, combinedOptionScope([parser]));
1929
1935
  const innerFacts = getEmptyInputFacts(parser);
1930
1936
  defineEmptyInputFacts(scopedNonEmptyParser, {
1937
+ ...innerFacts.probe == null ? {} : { probe: {
1938
+ step: "failure",
1939
+ fromInitial: innerFacts.probe.fromInitial
1940
+ } },
1931
1941
  step: "failure",
1932
1942
  ...innerFacts.fromInitial == null ? {} : { fromInitial: innerFacts.fromInitial }
1933
1943
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@optique/core",
3
- "version": "1.4.0-dev.2713",
3
+ "version": "1.4.0-dev.2716",
4
4
  "description": "Type-safe combinatorial command-line interface parser",
5
5
  "keywords": [
6
6
  "CLI",
@@ -245,7 +245,7 @@
245
245
  },
246
246
  "sideEffects": false,
247
247
  "devDependencies": {
248
- "@optique/env": "1.4.0-dev.2713+79907305",
248
+ "@optique/env": "1.4.0-dev.2716+57cbdb77",
249
249
  "@types/node": "^24.0.0",
250
250
  "fast-check": "^4.7.0",
251
251
  "tsdown": "^0.13.0",
@@ -234,9 +234,11 @@ Common mistakes checklist
234
234
 
235
235
  - Pass parsers to `run()` for apps; use explicit argument arrays with
236
236
  `parse()` in tests and embedded use. Do not pre-parse `process.argv`.
237
- - Do not treat `or(a, b)` as “zero or more alternatives.” It requires one
238
- matching branch unless the whole `or()` is wrapped in `optional()` or
239
- `withDefault()`.
237
+ - Declare only static facts with `defineEmptyInputBehavior()` in
238
+ `@optique/core/extension`; use `inheritEmptyInputBehavior()` for
239
+ transparent wrappers. See <https://optique.dev/concepts/extend.md>.
240
+ - Do not treat `or(a, b)` as “zero or more alternatives.” Wrap it in
241
+ `optional()` or `withDefault()` to allow no matching branch.
240
242
  - Do not use `object()` for mutually exclusive subcommands. Use
241
243
  `or(command(...), command(...))`.
242
244
  - Do not forget that `flag("--x")` is required. Wrap it in `optional()` or
@@ -252,10 +254,8 @@ Common mistakes checklist
252
254
  - Enable `showEnvironment` for env-only help; set `documentation.description`.
253
255
  - Keep multi-level dependency graphs with `dependency()` rather than
254
256
  duplicating one-level factories.
255
- - Do not probe runtime capabilities eagerly before constructing a prompt
256
- parser. Put synchronous or asynchronous checks in the prompt config's
257
- `when` field and provide a typed `otherwise` value. The check then runs
258
- only if parsing reaches the prompt fallback.
257
+ - Put runtime prompt checks in `when`, with typed `otherwise`; evaluate them
258
+ only at fallback, never eagerly during construction.
259
259
 
260
260
  For the detailed maintained guide, use <https://optique.dev/pitfalls.md>.
261
261