@pyreon/lint 0.12.0 → 0.12.1
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/lib/analysis/cli.js.html +1 -1
- package/lib/analysis/index.js.html +1 -1
- package/lib/cli.js +54 -2
- package/lib/cli.js.map +1 -1
- package/lib/index.js +54 -2
- package/lib/index.js.map +1 -1
- package/lib/types/index.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/rules/index.ts +4 -1
- package/src/rules/reactivity/no-signal-in-props.ts +53 -0
- package/src/runner.ts +15 -2
- package/src/tests/runner.test.ts +3 -3
package/lib/index.js
CHANGED
|
@@ -1745,6 +1745,48 @@ const noSignalInLoop = {
|
|
|
1745
1745
|
}
|
|
1746
1746
|
};
|
|
1747
1747
|
|
|
1748
|
+
//#endregion
|
|
1749
|
+
//#region src/rules/reactivity/no-signal-in-props.ts
|
|
1750
|
+
function isComponentTag(name) {
|
|
1751
|
+
return name.length > 0 && name[0] === name[0]?.toUpperCase() && name[0] !== name[0]?.toLowerCase();
|
|
1752
|
+
}
|
|
1753
|
+
/**
|
|
1754
|
+
* Warn when a known signal/computed is called in a component prop position.
|
|
1755
|
+
* Component props are evaluated once at mount — signal reads are NOT reactive
|
|
1756
|
+
* unless the compiler wraps them with _rp(). The compiler handles this
|
|
1757
|
+
* automatically, but this rule catches manual h() calls and educates developers.
|
|
1758
|
+
*/
|
|
1759
|
+
const noSignalInProps = {
|
|
1760
|
+
meta: {
|
|
1761
|
+
id: "pyreon/no-signal-in-props",
|
|
1762
|
+
category: "reactivity",
|
|
1763
|
+
description: "Signal call in component prop — value captured once unless compiler wraps it. Use props.x pattern for reactivity.",
|
|
1764
|
+
severity: "warn",
|
|
1765
|
+
fixable: false
|
|
1766
|
+
},
|
|
1767
|
+
create(context) {
|
|
1768
|
+
return { JSXExpressionContainer(node) {
|
|
1769
|
+
const expr = node.expression;
|
|
1770
|
+
if (!expr || expr.type !== "CallExpression") return;
|
|
1771
|
+
const callee = expr.callee;
|
|
1772
|
+
if (!callee || callee.type !== "Identifier") return;
|
|
1773
|
+
const source = context.getSourceText();
|
|
1774
|
+
let i = node.start - 1;
|
|
1775
|
+
while (i >= 0 && source[i] !== "<" && source[i] !== ">") i--;
|
|
1776
|
+
if (i < 0 || source[i] !== "<") return;
|
|
1777
|
+
const tagStart = i + 1;
|
|
1778
|
+
let tagEnd = tagStart;
|
|
1779
|
+
while (tagEnd < source.length && /[\w.]/.test(source[tagEnd] ?? "")) tagEnd++;
|
|
1780
|
+
const tagName = source.slice(tagStart, tagEnd);
|
|
1781
|
+
if (!tagName || !isComponentTag(tagName)) return;
|
|
1782
|
+
context.report({
|
|
1783
|
+
message: `Signal call in <${tagName}> prop — use props.x pattern inside the component for reactive access.`,
|
|
1784
|
+
span: getSpan(expr)
|
|
1785
|
+
});
|
|
1786
|
+
} };
|
|
1787
|
+
}
|
|
1788
|
+
};
|
|
1789
|
+
|
|
1748
1790
|
//#endregion
|
|
1749
1791
|
//#region src/rules/reactivity/no-signal-leak.ts
|
|
1750
1792
|
const noSignalLeak = {
|
|
@@ -2463,6 +2505,7 @@ const allRules = [
|
|
|
2463
2505
|
noBareSignalInJsx,
|
|
2464
2506
|
noContextDestructure,
|
|
2465
2507
|
noSignalInLoop,
|
|
2508
|
+
noSignalInProps,
|
|
2466
2509
|
noNestedEffect,
|
|
2467
2510
|
noPeekInTracked,
|
|
2468
2511
|
noUnbatchedUpdates,
|
|
@@ -2704,10 +2747,19 @@ function lintFile(filePath, sourceText, rules, config, cache) {
|
|
|
2704
2747
|
allCallbacks.push(rule.create(ctx));
|
|
2705
2748
|
}
|
|
2706
2749
|
new Visitor(mergeCallbacks(allCallbacks)).visit(program);
|
|
2707
|
-
|
|
2750
|
+
const lines = sourceText.split("\n");
|
|
2751
|
+
const filtered = diagnostics.filter((d) => {
|
|
2752
|
+
const prevLineIdx = d.loc.line - 2;
|
|
2753
|
+
if (prevLineIdx < 0) return true;
|
|
2754
|
+
const prevLine = lines[prevLineIdx]?.trim();
|
|
2755
|
+
if (!prevLine?.startsWith("// pyreon-lint-ignore")) return true;
|
|
2756
|
+
const rest = prevLine.slice(21).trim();
|
|
2757
|
+
return rest.length > 0 && rest !== d.ruleId;
|
|
2758
|
+
});
|
|
2759
|
+
filtered.sort((a, b) => a.span.start - b.span.start);
|
|
2708
2760
|
return {
|
|
2709
2761
|
filePath,
|
|
2710
|
-
diagnostics
|
|
2762
|
+
diagnostics: filtered
|
|
2711
2763
|
};
|
|
2712
2764
|
}
|
|
2713
2765
|
/**
|