@foldkit/oxlint-plugin 0.9.0-canary.f59da5146480 → 0.10.0-canary.4e7d8d4010be

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/all.json CHANGED
@@ -20,6 +20,7 @@
20
20
  "foldkit/no-duplicate-onmount-per-element": "error",
21
21
  "foldkit/no-empty-children-array": "error",
22
22
  "foldkit/no-empty-commands-array": "error",
23
+ "foldkit/no-empty-to-parent-out-message": "error",
23
24
  "foldkit/no-empty-object-tagged-call": "error",
24
25
  "foldkit/no-hand-rolled-command-struct": "error",
25
26
  "foldkit/no-hardcoded-route-strings": "error",
@@ -74,6 +75,7 @@
74
75
  "foldkit/no-duplicate-onmount-per-element": "off",
75
76
  "foldkit/no-empty-children-array": "off",
76
77
  "foldkit/no-empty-commands-array": "off",
78
+ "foldkit/no-empty-to-parent-out-message": "off",
77
79
  "foldkit/no-empty-object-tagged-call": "off",
78
80
  "foldkit/no-hand-rolled-command-struct": "off",
79
81
  "foldkit/no-hardcoded-route-strings": "off",
package/dist/index.d.ts CHANGED
@@ -30,6 +30,7 @@ declare const _default: {
30
30
  readonly 'no-duplicate-onmount-per-element': import("effect-oxlint").CreateRule;
31
31
  readonly 'no-empty-children-array': import("effect-oxlint").CreateRule;
32
32
  readonly 'no-empty-commands-array': import("effect-oxlint").CreateRule;
33
+ readonly 'no-empty-to-parent-out-message': import("effect-oxlint").CreateRule;
33
34
  readonly 'no-empty-object-tagged-call': import("effect-oxlint").CreateRule;
34
35
  readonly 'no-hand-rolled-command-struct': import("effect-oxlint").CreateRule;
35
36
  readonly 'no-hardcoded-route-strings': import("effect-oxlint").CreateRule;
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,eAAe,CAAA;AA+DtC,KAAK,QAAQ,GAAG,QAAQ,CAAC;IACvB,KAAK,EAAE,KAAK,CAAC,MAAM,CAAC,CAAA;IACpB,YAAY,CAAC,EAAE,KAAK,CAAC,MAAM,CAAC,CAAA;IAC5B,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,YAAY,CAAC,CAAA;CAC3C,CAAC,CAAA;AAEF,KAAK,gBAAgB,GAAG,MAAM,CAAC,YAAY,GAAG;IAC5C,SAAS,EAAE,KAAK,CAAC,QAAQ,CAAC,CAAA;CAC3B,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAiDD,wBAMC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,eAAe,CAAA;AAiEtC,KAAK,QAAQ,GAAG,QAAQ,CAAC;IACvB,KAAK,EAAE,KAAK,CAAC,MAAM,CAAC,CAAA;IACpB,YAAY,CAAC,EAAE,KAAK,CAAC,MAAM,CAAC,CAAA;IAC5B,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,YAAY,CAAC,CAAA;CAC3C,CAAC,CAAA;AAEF,KAAK,gBAAgB,GAAG,MAAM,CAAC,YAAY,GAAG;IAC5C,SAAS,EAAE,KAAK,CAAC,QAAQ,CAAC,CAAA;CAC3B,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAiDD,wBAMC"}
package/dist/index.js CHANGED
@@ -11086,92 +11086,134 @@ var lazyViewStableReferences = Rule_exports.define({
11086
11086
 
11087
11087
  // src/rules/mount-factory-must-use-element.ts
11088
11088
  var MOUNT_DEFINITION_METHODS = ["define", "defineStream"];
11089
- var NO_ELEMENT_PARAMETER_MESSAGE = "This Mount factory never receives the element: it declares no usable element parameter. A Mount exists for element-caused, element-targeted work. If the element is irrelevant, use a Command, Subscription, or ManagedResource instead.";
11090
- var ignoredElementParameterMessage = (parameterName) => `The element parameter \`${parameterName}\` is named as ignored. A Mount factory must read or write its live element; if the element does not matter here, the side effect has a different cause and belongs in a Command, Subscription, or ManagedResource.`;
11091
- var unusedElementParameterMessage = (parameterName) => `The element parameter \`${parameterName}\` is never referenced in this Mount factory. A Mount factory must use its live element; work that does not need the element belongs in a Command, Subscription, or ManagedResource.`;
11092
- var isFactoryFunction = (node) => node.type === "ArrowFunctionExpression" || node.type === "FunctionExpression";
11093
- var isMountDefinitionChain = (call) => {
11094
- if (call.callee.type === "MemberExpression") {
11095
- return AST_exports.isMember(call.callee, "Mount", MOUNT_DEFINITION_METHODS);
11096
- }
11097
- if (call.callee.type === "CallExpression") {
11098
- return isMountDefinitionChain(call.callee);
11099
- }
11100
- return false;
11101
- };
11102
- var returnedFactoryFunction = (statement) => {
11103
- if (statement.type !== "ReturnStatement" || statement.argument === null) {
11104
- return Option_exports.none();
11105
- }
11106
- return isFactoryFunction(statement.argument) ? Option_exports.some(statement.argument) : Option_exports.none();
11107
- };
11108
- var descendToElementFactory = (builder) => {
11109
- const body = builder.body;
11110
- if (body === null) return builder;
11111
- if (isFactoryFunction(body)) return body;
11112
- if (body.type === "BlockStatement") {
11113
- return pipe(
11114
- body.body,
11115
- Array_exports.findFirst(returnedFactoryFunction),
11116
- Option_exports.getOrElse(() => builder)
11117
- );
11118
- }
11119
- return builder;
11120
- };
11089
+ var ELEMENT_FIELD = "element";
11090
+ var NO_ELEMENT_BINDING_MESSAGE = `This Mount's \`execute\` never receives the element: it does not destructure \`element\` from its input. A Mount exists for element-caused, element-targeted work. If the element is irrelevant, use a Command, Subscription, or ManagedResource instead.`;
11091
+ var ignoredElementBindingMessage = (bindingName) => `The element binding \`${bindingName}\` is named as ignored. A Mount's \`execute\` must read or write its live element; if the element does not matter here, the side effect has a different cause and belongs in a Command, Subscription, or ManagedResource.`;
11092
+ var unusedElementBindingMessage = (bindingName) => `The element binding \`${bindingName}\` is never referenced in this Mount's \`execute\`. A Mount's \`execute\` must use its live element; work that does not need the element belongs in a Command, Subscription, or ManagedResource.`;
11093
+ var unreadElementFieldMessage = (bindingName) => `This Mount's \`execute\` reads other fields off \`${bindingName}\` but never its \`element\`. Destructure \`element\` from the input so the check can see the read, or if the element is irrelevant, use a Command, Subscription, or ManagedResource instead.`;
11094
+ var unusedInputMessage = (bindingName) => `This Mount's \`execute\` never references \`${bindingName}\`, so it never reaches the live element. Destructure \`element\` from the input so the check can see the read, or if the element is irrelevant, use a Command, Subscription, or ManagedResource instead.`;
11095
+ var isExecuteFunction = (node) => node.type === "ArrowFunctionExpression" || node.type === "FunctionExpression";
11121
11096
  var isRecord = (value) => typeof value === "object" && value !== null;
11122
- var shadowsParameterName = (functionNode, parameterName) => {
11097
+ var shadowsBindingName = (functionNode, bindingName) => {
11123
11098
  const parameters = functionNode.params;
11124
11099
  if (!isRecord(parameters)) return false;
11125
11100
  return Object.values(parameters).some(
11126
- (parameter) => isRecord(parameter) && parameter.type === "Identifier" && parameter.name === parameterName
11101
+ (parameter) => isRecord(parameter) && parameter.type === "Identifier" && parameter.name === bindingName
11127
11102
  );
11128
11103
  };
11129
- var referencesName = (value, parameterName) => {
11104
+ var referencesName = (value, bindingName) => {
11130
11105
  if (!isRecord(value)) return false;
11131
- if (value.type === "Identifier" && value.name === parameterName) return true;
11132
- if ((value.type === "ArrowFunctionExpression" || value.type === "FunctionExpression") && shadowsParameterName(value, parameterName)) {
11106
+ if (value.type === "Identifier" && value.name === bindingName) return true;
11107
+ if ((value.type === "ArrowFunctionExpression" || value.type === "FunctionExpression") && shadowsBindingName(value, bindingName)) {
11133
11108
  return false;
11134
11109
  }
11110
+ if (value.type === "MemberExpression" && value.computed !== true) {
11111
+ return referencesName(value.object, bindingName);
11112
+ }
11135
11113
  if (value.type === "Property") {
11136
- const computedKeyReferences = value.computed === true && referencesName(value.key, parameterName);
11137
- return computedKeyReferences || referencesName(value.value, parameterName);
11114
+ const computedKeyReferences = value.computed === true && referencesName(value.key, bindingName);
11115
+ return computedKeyReferences || referencesName(value.value, bindingName);
11138
11116
  }
11139
11117
  return Object.entries(value).some(
11140
- ([key, child]) => key !== "parent" && referencesName(child, parameterName)
11118
+ ([key, child]) => key !== "parent" && referencesName(child, bindingName)
11119
+ );
11120
+ };
11121
+ var inputUses = (value, inputName) => {
11122
+ if (!isRecord(value)) return [];
11123
+ if (value.type === "MemberExpression" && isRecord(value.object) && value.object.type === "Identifier" && value.object.name === inputName) {
11124
+ if (value.computed === true) return ["MaybeElement"];
11125
+ return isRecord(value.property) && value.property.type === "Identifier" && value.property.name === ELEMENT_FIELD ? ["Element"] : ["OtherField"];
11126
+ }
11127
+ if (value.type === "MemberExpression" && value.computed !== true) {
11128
+ return inputUses(value.object, inputName);
11129
+ }
11130
+ if ((value.type === "ArrowFunctionExpression" || value.type === "FunctionExpression") && shadowsBindingName(value, inputName)) {
11131
+ return [];
11132
+ }
11133
+ if (value.type === "Property") {
11134
+ const keyUses = value.computed === true ? inputUses(value.key, inputName) : [];
11135
+ return [...keyUses, ...inputUses(value.value, inputName)];
11136
+ }
11137
+ if (value.type === "Identifier" && value.name === inputName) {
11138
+ return ["MaybeElement"];
11139
+ }
11140
+ return Object.entries(value).flatMap(
11141
+ ([key, child]) => key === "parent" ? [] : inputUses(child, inputName)
11141
11142
  );
11142
11143
  };
11143
- var elementFactoryDiagnostic = (factory) => {
11144
- const [firstParameter] = factory.params;
11145
- if (firstParameter === void 0 || firstParameter.type !== "Identifier") {
11146
- return Option_exports.some(NO_ELEMENT_PARAMETER_MESSAGE);
11144
+ var elementProperty = (pattern) => pipe(
11145
+ pattern.properties,
11146
+ Array_exports.findFirst(
11147
+ (property) => property.type === "Property" && (property.key.type === "Literal" ? property.key.value === ELEMENT_FIELD : !property.computed && property.key.type === "Identifier" && property.key.name === ELEMENT_FIELD)
11148
+ )
11149
+ );
11150
+ var restBindingName = (pattern) => pipe(
11151
+ pattern.properties,
11152
+ Array_exports.findFirst(
11153
+ (property) => property.type === "RestElement" && property.argument.type === "Identifier" ? Option_exports.some(property.argument.name) : Option_exports.none()
11154
+ )
11155
+ );
11156
+ var withoutDefault = (value) => value.type === "AssignmentPattern" ? value.left : value;
11157
+ var bindingDiagnostic = (execute, bindingName) => {
11158
+ if (bindingName.startsWith("_")) {
11159
+ return Option_exports.some(ignoredElementBindingMessage(bindingName));
11147
11160
  }
11148
- if (firstParameter.name.startsWith("_")) {
11149
- return Option_exports.some(ignoredElementParameterMessage(firstParameter.name));
11161
+ return referencesName(execute.body, bindingName) ? Option_exports.none() : Option_exports.some(unusedElementBindingMessage(bindingName));
11162
+ };
11163
+ var unpackedDiagnostic = (execute, bindingName) => {
11164
+ const uses = inputUses(execute.body, bindingName);
11165
+ if (Array_exports.isReadonlyArrayEmpty(uses)) {
11166
+ return Option_exports.some(unusedInputMessage(bindingName));
11150
11167
  }
11151
- return referencesName(factory.body, firstParameter.name) ? Option_exports.none() : Option_exports.some(unusedElementParameterMessage(firstParameter.name));
11168
+ return uses.every((use) => use === "OtherField") ? Option_exports.some(unreadElementFieldMessage(bindingName)) : Option_exports.none();
11152
11169
  };
11170
+ var executeDiagnostic = (execute) => {
11171
+ const [firstParameter] = execute.params;
11172
+ if (firstParameter === void 0) {
11173
+ return Option_exports.some(NO_ELEMENT_BINDING_MESSAGE);
11174
+ }
11175
+ if (firstParameter.type === "Identifier") {
11176
+ return unpackedDiagnostic(execute, firstParameter.name);
11177
+ }
11178
+ if (firstParameter.type !== "ObjectPattern") {
11179
+ return Option_exports.some(NO_ELEMENT_BINDING_MESSAGE);
11180
+ }
11181
+ return Option_exports.match(elementProperty(firstParameter), {
11182
+ onNone: () => Option_exports.match(restBindingName(firstParameter), {
11183
+ onNone: () => Option_exports.some(NO_ELEMENT_BINDING_MESSAGE),
11184
+ onSome: (restName) => unpackedDiagnostic(execute, restName)
11185
+ }),
11186
+ onSome: (property) => {
11187
+ const pattern = withoutDefault(property.value);
11188
+ return pattern.type === "Identifier" ? bindingDiagnostic(execute, pattern.name) : Option_exports.none();
11189
+ }
11190
+ });
11191
+ };
11192
+ var executeFunction = (node) => pipe(
11193
+ Array_exports.get(node.arguments, 1),
11194
+ Option_exports.filter(isObjectExpression),
11195
+ Option_exports.flatMap((config) => AST_exports.objectGetValue(config, "execute")),
11196
+ Option_exports.filter(isExecuteFunction)
11197
+ );
11153
11198
  var mountFactoryMustUseElement = Rule_exports.define({
11154
11199
  name: "mount-factory-must-use-element",
11155
11200
  meta: Rule_exports.meta({
11156
11201
  type: "suggestion",
11157
- description: "Require Mount factories to use their live element."
11202
+ description: "Require a Mount's execute to use its live element."
11158
11203
  }),
11159
11204
  create: function* () {
11160
11205
  const ctx = yield* RuleContext;
11161
11206
  return {
11162
11207
  CallExpression: (node) => {
11163
- if (!isCallExpression(node)) return Effect_exports.void;
11164
- if (node.callee.type !== "CallExpression" || !isMountDefinitionChain(node.callee)) {
11165
- return Effect_exports.void;
11166
- }
11167
- const [factoryArgument] = node.arguments;
11168
- if (factoryArgument === void 0 || !isFactoryFunction(factoryArgument)) {
11208
+ if (!isCallExpression(node) || !AST_exports.isCallOf(node, "Mount", MOUNT_DEFINITION_METHODS)) {
11169
11209
  return Effect_exports.void;
11170
11210
  }
11171
- const elementFactory = descendToElementFactory(factoryArgument);
11172
- return Option_exports.match(elementFactoryDiagnostic(elementFactory), {
11211
+ return Option_exports.match(executeFunction(node), {
11173
11212
  onNone: () => Effect_exports.void,
11174
- onSome: (message) => ctx.report(Diagnostic_exports.make({ node: elementFactory, message }))
11213
+ onSome: (execute) => Option_exports.match(executeDiagnostic(execute), {
11214
+ onNone: () => Effect_exports.void,
11215
+ onSome: (message) => ctx.report(Diagnostic_exports.make({ node: execute, message }))
11216
+ })
11175
11217
  });
11176
11218
  }
11177
11219
  };
@@ -11456,7 +11498,7 @@ var noDisablingDevGuardrails = Rule_exports.define({
11456
11498
  });
11457
11499
 
11458
11500
  // src/rules/no-duplicate-onmount-per-element.ts
11459
- var DUPLICATE_ON_MOUNT_MESSAGE = "Only one OnMount attribute can attach to an element. Foldkit installs a single insert hook and tracks a single Mount fiber per element, so a later OnMount replaces the earlier one and its factory never runs. Combine the behaviors into one Mount definition.";
11501
+ var DUPLICATE_ON_MOUNT_MESSAGE = "Only one OnMount attribute can attach to an element. Foldkit installs a single insert hook and tracks a single Mount fiber per element, so a later OnMount replaces the earlier one and its `execute` never runs. Combine the behaviors into one Mount definition.";
11460
11502
  var isOnMountCallee = (callee) => {
11461
11503
  if (callee.type === "Identifier") {
11462
11504
  return callee.name === "OnMount";
@@ -11792,7 +11834,7 @@ var staticPropertyName2 = (property) => {
11792
11834
  return staticStringValue(property.key);
11793
11835
  };
11794
11836
  var isEmptyCommandsProperty = (property) => Option_exports.contains(staticPropertyName2(property), COMMANDS_PROPERTY) && isArrayExpression(property.value) && Array_exports.isReadonlyArrayEmpty(property.value.elements);
11795
- var removalRange = (object, property, index) => {
11837
+ var removalRange = (sourceText, object, property, index) => {
11796
11838
  const maybePrevious = Array_exports.get(object.properties, index - 1);
11797
11839
  if (Option_exports.isSome(maybePrevious)) {
11798
11840
  return [maybePrevious.value.end, property.end];
@@ -11801,7 +11843,15 @@ var removalRange = (object, property, index) => {
11801
11843
  if (Option_exports.isSome(maybeNext)) {
11802
11844
  return [property.start, maybeNext.value.start];
11803
11845
  }
11804
- return property.range;
11846
+ const trailingComma = /^\s*,/.exec(sourceText.slice(property.end, object.end));
11847
+ if (trailingComma === null) {
11848
+ return property.range;
11849
+ }
11850
+ const maybeMatchedText = Array_exports.head(trailingComma);
11851
+ if (Option_exports.isNone(maybeMatchedText)) {
11852
+ return property.range;
11853
+ }
11854
+ return [property.start, property.end + maybeMatchedText.value.length];
11805
11855
  };
11806
11856
  var emptyCommandsMessage = "Return { model } instead of the literal { model, commands: [] }. Return a computed Commands collection directly, even when it may be empty.";
11807
11857
  var noEmptyCommandsArray = Rule_exports.define({
@@ -11822,6 +11872,10 @@ var noEmptyCommandsArray = Rule_exports.define({
11822
11872
  node.properties,
11823
11873
  (property) => property.type === "SpreadElement"
11824
11874
  );
11875
+ const hasDynamicComputedProperty = Array_exports.some(
11876
+ node.properties,
11877
+ (property) => isObjectProperty(property) && property.computed && Option_exports.isNone(staticPropertyName2(property))
11878
+ );
11825
11879
  const commandProperties = Array_exports.filter(
11826
11880
  node.properties,
11827
11881
  (property) => isObjectProperty(property) && Option_exports.contains(staticPropertyName2(property), COMMANDS_PROPERTY)
@@ -11839,13 +11893,15 @@ var noEmptyCommandsArray = Rule_exports.define({
11839
11893
  node: property,
11840
11894
  message: emptyCommandsMessage
11841
11895
  });
11842
- if (hasSpread || hasComments || Array_exports.isReadonlyArrayNonEmpty(Array_exports.drop(commandProperties, 1))) {
11896
+ if (hasSpread || hasDynamicComputedProperty || hasComments || Array_exports.isReadonlyArrayNonEmpty(Array_exports.drop(commandProperties, 1))) {
11843
11897
  return ctx.report(diagnostic);
11844
11898
  }
11845
11899
  return ctx.report(
11846
11900
  Diagnostic_exports.withFix(
11847
11901
  diagnostic,
11848
- (fixer) => fixer.removeRange(removalRange(node, property, index))
11902
+ (fixer) => fixer.removeRange(
11903
+ removalRange(ctx.sourceCode.text, node, property, index)
11904
+ )
11849
11905
  )
11850
11906
  );
11851
11907
  },
@@ -11936,6 +11992,135 @@ var noEmptyObjectTaggedCall = Rule_exports.define({
11936
11992
  }
11937
11993
  });
11938
11994
 
11995
+ // src/rules/no-empty-to-parent-out-message.ts
11996
+ var TO_PARENT_OUT_MESSAGE_PROPERTY = "toParentOutMessage";
11997
+ var isObjectProperty2 = (node) => typeof node === "object" && node !== null && "type" in node && node.type === "Property";
11998
+ var staticPropertyName3 = (property) => {
11999
+ if (!property.computed && isIdentifier(property.key)) {
12000
+ return Option_exports.some(property.key.name);
12001
+ }
12002
+ return staticStringValue(property.key);
12003
+ };
12004
+ var isArrowFunctionExpression3 = (node) => typeof node === "object" && node !== null && "type" in node && node.type === "ArrowFunctionExpression";
12005
+ var isFunctionExpression = (node) => typeof node === "object" && node !== null && "type" in node && node.type === "FunctionExpression";
12006
+ var isMapperFunction = (node) => isArrowFunctionExpression3(node) && !node.async || isFunctionExpression(node) && !node.async && !node.generator;
12007
+ var isIdentifierReference = (node) => isIdentifier(node);
12008
+ var indexReferences = (scopes) => {
12009
+ const references = /* @__PURE__ */ new WeakMap();
12010
+ for (const scope3 of scopes) {
12011
+ for (const reference of scope3.references) {
12012
+ references.set(reference.identifier, reference);
12013
+ }
12014
+ }
12015
+ return references;
12016
+ };
12017
+ var isUnshadowedUndefined = (references, node) => {
12018
+ if (!isIdentifierReference(node) || node.name !== "undefined") {
12019
+ return false;
12020
+ }
12021
+ if (references === void 0) {
12022
+ return true;
12023
+ }
12024
+ const reference = references.get(node);
12025
+ return reference !== void 0 && (reference.resolved === null || Array_exports.isArrayEmpty(reference.resolved.defs));
12026
+ };
12027
+ var directlyReturnsUndefined = (references, mapper) => {
12028
+ if (mapper.body === null) {
12029
+ return false;
12030
+ }
12031
+ if (mapper.body.type !== "BlockStatement") {
12032
+ return isUnshadowedUndefined(references, mapper.body);
12033
+ }
12034
+ const [firstStatement, secondStatement] = mapper.body.body;
12035
+ return firstStatement?.type === "ReturnStatement" && secondStatement === void 0 && isUnshadowedUndefined(references, firstStatement.argument);
12036
+ };
12037
+ var isEmptyMapperProperty = (references, property) => property.kind === "init" && Option_exports.contains(
12038
+ staticPropertyName3(property),
12039
+ TO_PARENT_OUT_MESSAGE_PROPERTY
12040
+ ) && isMapperFunction(property.value) && directlyReturnsUndefined(references, property.value);
12041
+ var removalRange2 = (sourceText, object, property, index) => {
12042
+ const maybePrevious = Array_exports.get(object.properties, index - 1);
12043
+ if (Option_exports.isSome(maybePrevious)) {
12044
+ return [maybePrevious.value.end, property.end];
12045
+ }
12046
+ const maybeNext = Array_exports.get(object.properties, index + 1);
12047
+ if (Option_exports.isSome(maybeNext)) {
12048
+ return [property.start, maybeNext.value.start];
12049
+ }
12050
+ const trailingComma = /^\s*,/.exec(sourceText.slice(property.end, object.end));
12051
+ if (trailingComma === null) {
12052
+ return property.range;
12053
+ }
12054
+ const maybeMatchedText = Array_exports.head(trailingComma);
12055
+ if (Option_exports.isNone(maybeMatchedText)) {
12056
+ return property.range;
12057
+ }
12058
+ return [property.start, property.end + maybeMatchedText.value.length];
12059
+ };
12060
+ var emptyMapperMessage = "Omit toParentOutMessage. This mapper directly returns undefined, so it forwards nothing to the parent.";
12061
+ var noEmptyToParentOutMessage = Rule_exports.define({
12062
+ name: "no-empty-to-parent-out-message",
12063
+ meta: Rule_exports.meta({
12064
+ type: "suggestion",
12065
+ description: "Omit an inline toParentOutMessage mapper that directly returns undefined.",
12066
+ fixable: "code"
12067
+ }),
12068
+ create: function* () {
12069
+ const ctx = yield* RuleContext;
12070
+ const scopes = ctx.sourceCode.scopeManager?.scopes;
12071
+ const references = scopes === void 0 ? void 0 : indexReferences(scopes);
12072
+ return {
12073
+ ObjectExpression: (node) => {
12074
+ if (!isObjectExpression(node)) {
12075
+ return Effect_exports.void;
12076
+ }
12077
+ const hasSpread = Array_exports.some(
12078
+ node.properties,
12079
+ (property) => property.type === "SpreadElement"
12080
+ );
12081
+ const hasDynamicComputedProperty = Array_exports.some(
12082
+ node.properties,
12083
+ (property) => isObjectProperty2(property) && property.computed && Option_exports.isNone(staticPropertyName3(property))
12084
+ );
12085
+ const mapperProperties = Array_exports.filter(
12086
+ node.properties,
12087
+ (property) => isObjectProperty2(property) && Option_exports.contains(
12088
+ staticPropertyName3(property),
12089
+ TO_PARENT_OUT_MESSAGE_PROPERTY
12090
+ )
12091
+ );
12092
+ const hasComments = Array_exports.isReadonlyArrayNonEmpty(
12093
+ ctx.sourceCode.getCommentsInside(node)
12094
+ );
12095
+ return Effect_exports.forEach(
12096
+ node.properties.entries(),
12097
+ ([index, property]) => {
12098
+ if (!isObjectProperty2(property) || !isEmptyMapperProperty(references, property)) {
12099
+ return Effect_exports.void;
12100
+ }
12101
+ const diagnostic = Diagnostic_exports.make({
12102
+ node: property,
12103
+ message: emptyMapperMessage
12104
+ });
12105
+ if (hasSpread || hasDynamicComputedProperty || hasComments || Array_exports.isReadonlyArrayNonEmpty(Array_exports.drop(mapperProperties, 1))) {
12106
+ return ctx.report(diagnostic);
12107
+ }
12108
+ return ctx.report(
12109
+ Diagnostic_exports.withFix(
12110
+ diagnostic,
12111
+ (fixer) => fixer.removeRange(
12112
+ removalRange2(ctx.sourceCode.text, node, property, index)
12113
+ )
12114
+ )
12115
+ );
12116
+ },
12117
+ { discard: true }
12118
+ );
12119
+ }
12120
+ };
12121
+ }
12122
+ });
12123
+
11939
12124
  // src/rules/no-hand-rolled-command-struct.ts
11940
12125
  var COMMAND_STRUCT_SHAPES = [
11941
12126
  ["name", "effect"],
@@ -12151,7 +12336,7 @@ var staticMemberName = (node) => {
12151
12336
  }
12152
12337
  return void 0;
12153
12338
  };
12154
- var staticPropertyName3 = (property) => {
12339
+ var staticPropertyName4 = (property) => {
12155
12340
  if (property.key.type === "Identifier") {
12156
12341
  return property.key.name;
12157
12342
  }
@@ -12178,7 +12363,7 @@ var restrictedGlobalDiagnostic = (node, name) => Diagnostic_exports.make({
12178
12363
  node,
12179
12364
  message: `Global \`${name}\` is not portable across Foldkit server targets. Use a server API available everywhere you deploy or pass the value into the entry.`
12180
12365
  });
12181
- var indexReferences = (scopes) => {
12366
+ var indexReferences2 = (scopes) => {
12182
12367
  const references = /* @__PURE__ */ new WeakMap();
12183
12368
  for (const scope3 of scopes) {
12184
12369
  for (const reference of scope3.references) {
@@ -12199,7 +12384,7 @@ var noNonportableServerGlobals = Rule_exports.define({
12199
12384
  }),
12200
12385
  create: function* () {
12201
12386
  const ctx = yield* RuleContext;
12202
- const references = indexReferences(ctx.sourceCode.scopeManager.scopes);
12387
+ const references = indexReferences2(ctx.sourceCode.scopeManager.scopes);
12203
12388
  return {
12204
12389
  Identifier: (node) => {
12205
12390
  if (node.type !== "Identifier" || !restrictedGlobalNames.has(node.name) || isInsideTypeQuery(node)) {
@@ -12229,7 +12414,7 @@ var noNonportableServerGlobals = Rule_exports.define({
12229
12414
  if (property.type !== "Property") {
12230
12415
  return [];
12231
12416
  }
12232
- const name = staticPropertyName3(property);
12417
+ const name = staticPropertyName4(property);
12233
12418
  return name !== void 0 && restrictedGlobalNames.has(name) ? [{ name, property }] : [];
12234
12419
  });
12235
12420
  return isUnshadowedGlobalReference(references, source) ? Effect_exports.forEach(
@@ -12816,7 +13001,7 @@ var gotMessagePattern = /^Got\w*Message$/;
12816
13001
  var dynamicCalleeLabel = "<dynamic call>";
12817
13002
  var isWrapperExpression3 = (node) => typeof node === "object" && node !== null && "type" in node && typeof node.type === "string" && mapperWrapperTypes.includes(node.type) && "expression" in node;
12818
13003
  var unwrapExpression3 = (node) => isWrapperExpression3(node) ? unwrapExpression3(node.expression) : node;
12819
- var isArrowFunctionExpression3 = (node) => typeof node === "object" && node !== null && "type" in node && node.type === "ArrowFunctionExpression";
13004
+ var isArrowFunctionExpression4 = (node) => typeof node === "object" && node !== null && "type" in node && node.type === "ArrowFunctionExpression";
12820
13005
  var isBlockStatement2 = (node) => typeof node === "object" && node !== null && "type" in node && node.type === "BlockStatement";
12821
13006
  var isReturnStatement3 = (node) => typeof node === "object" && node !== null && "type" in node && node.type === "ReturnStatement";
12822
13007
  var resolveMemberPath2 = (node) => {
@@ -12843,7 +13028,7 @@ var commandMapperArrow = (node) => pipe(
12843
13028
  Option_exports.filter((calleePath) => commandMapperPaths.includes(calleePath)),
12844
13029
  Option_exports.flatMap(() => {
12845
13030
  const [, mapperArgument] = node.arguments;
12846
- return isArrowFunctionExpression3(mapperArgument) ? Option_exports.some(mapperArgument) : Option_exports.none();
13031
+ return isArrowFunctionExpression4(mapperArgument) ? Option_exports.some(mapperArgument) : Option_exports.none();
12847
13032
  })
12848
13033
  );
12849
13034
  var subscriptionLiftMapperArrow = (node) => {
@@ -12868,7 +13053,7 @@ var subscriptionLiftMapperArrow = (node) => {
12868
13053
  );
12869
13054
  }),
12870
13055
  Option_exports.flatMap(
12871
- (mapperValue) => isArrowFunctionExpression3(mapperValue) ? Option_exports.some(mapperValue) : Option_exports.none()
13056
+ (mapperValue) => isArrowFunctionExpression4(mapperValue) ? Option_exports.some(mapperValue) : Option_exports.none()
12872
13057
  )
12873
13058
  );
12874
13059
  };
@@ -12970,6 +13155,7 @@ var basePlugin = Plugin_exports.define({
12970
13155
  "no-duplicate-onmount-per-element": noDuplicateOnmountPerElement,
12971
13156
  "no-empty-children-array": noEmptyChildrenArray,
12972
13157
  "no-empty-commands-array": noEmptyCommandsArray,
13158
+ "no-empty-to-parent-out-message": noEmptyToParentOutMessage,
12973
13159
  "no-empty-object-tagged-call": noEmptyObjectTaggedCall,
12974
13160
  "no-hand-rolled-command-struct": noHandRolledCommandStruct,
12975
13161
  "no-hardcoded-route-strings": noHardcodedRouteStrings,
@@ -1,3 +1,3 @@
1
- /** Flags applied Mount.define and Mount.defineStream factories that never use their element parameter. A Mount exists for element-caused, element-targeted work; a factory that ignores the element belongs in a Command, Subscription, or ManagedResource. */
1
+ /** Flags Mount.define and Mount.defineStream definitions whose `execute` never uses its element. A Mount exists for element-caused, element-targeted work; work that ignores the element belongs in a Command, Subscription, or ManagedResource. */
2
2
  export declare const mountFactoryMustUseElement: import("effect-oxlint").CreateRule;
3
3
  //# sourceMappingURL=mount-factory-must-use-element.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"mount-factory-must-use-element.d.ts","sourceRoot":"","sources":["../../src/rules/mount-factory-must-use-element.ts"],"names":[],"mappings":"AA4GA,+PAA+P;AAC/P,eAAO,MAAM,0BAA0B,oCAiCrC,CAAA"}
1
+ {"version":3,"file":"mount-factory-must-use-element.d.ts","sourceRoot":"","sources":["../../src/rules/mount-factory-must-use-element.ts"],"names":[],"mappings":"AA0NA,oPAAoP;AACpP,eAAO,MAAM,0BAA0B,oCA4BrC,CAAA"}
@@ -1 +1 @@
1
- {"version":3,"file":"no-empty-commands-array.d.ts","sourceRoot":"","sources":["../../src/rules/no-empty-commands-array.ts"],"names":[],"mappings":"AAqDA;;;2EAG2E;AAC3E,eAAO,MAAM,oBAAoB,oCA+D/B,CAAA"}
1
+ {"version":3,"file":"no-empty-commands-array.d.ts","sourceRoot":"","sources":["../../src/rules/no-empty-commands-array.ts"],"names":[],"mappings":"AA+DA;;;2EAG2E;AAC3E,eAAO,MAAM,oBAAoB,oCAyE/B,CAAA"}
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Flags an inline `toParentOutMessage` mapper that directly returns
3
+ * `undefined`. That mapper forwards nothing to the parent, so the property
4
+ * should be omitted. This syntax-only rule does not inspect async functions,
5
+ * generators, getters, setters, or mappers referenced by name.
6
+ */
7
+ export declare const noEmptyToParentOutMessage: import("effect-oxlint").CreateRule;
8
+ //# sourceMappingURL=no-empty-to-parent-out-message.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"no-empty-to-parent-out-message.d.ts","sourceRoot":"","sources":["../../src/rules/no-empty-to-parent-out-message.ts"],"names":[],"mappings":"AAsJA;;;;;GAKG;AACH,eAAO,MAAM,yBAAyB,oCAgFpC,CAAA"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@foldkit/oxlint-plugin",
3
- "version": "0.9.0-canary.f59da5146480",
3
+ "version": "0.10.0-canary.4e7d8d4010be",
4
4
  "description": "Oxlint rules for Foldkit applications",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
package/recommended.json CHANGED
@@ -20,6 +20,7 @@
20
20
  "foldkit/no-duplicate-onmount-per-element": "error",
21
21
  "foldkit/no-empty-children-array": "error",
22
22
  "foldkit/no-empty-commands-array": "error",
23
+ "foldkit/no-empty-to-parent-out-message": "error",
23
24
  "foldkit/no-empty-object-tagged-call": "error",
24
25
  "foldkit/no-hand-rolled-command-struct": "error",
25
26
  "foldkit/no-hardcoded-route-strings": "error",
@@ -74,6 +75,7 @@
74
75
  "foldkit/no-duplicate-onmount-per-element": "off",
75
76
  "foldkit/no-empty-children-array": "off",
76
77
  "foldkit/no-empty-commands-array": "off",
78
+ "foldkit/no-empty-to-parent-out-message": "off",
77
79
  "foldkit/no-empty-object-tagged-call": "off",
78
80
  "foldkit/no-hand-rolled-command-struct": "off",
79
81
  "foldkit/no-hardcoded-route-strings": "off",