Skip to content

Commit 52ecd3c

Browse files
authored
Merge pull request #82 from macarie/observers
Silence reactivity warnings in (standard Web APIs) `*Observer` callbacks
2 parents 75a0dc7 + 07256ee commit 52ecd3c

File tree

3 files changed

+30
-5
lines changed

3 files changed

+30
-5
lines changed

docs/reactivity.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -535,6 +535,13 @@ setImmediate(() => console.log(signal()));
535535
requestAnimationFrame(() => console.log(signal()));
536536
requestIdleCallback(() => console.log(signal()));
537537

538+
const [signal] = createSignal(5);
539+
new IntersectionObserver(() => console.log(signal()));
540+
new MutationObserver(() => console.log(signal()));
541+
new PerformanceObserver(() => console.log(signal()));
542+
new ReportingObserver(() => console.log(signal()));
543+
new ResizeObserver(() => console.log(signal()));
544+
538545
const [photos, setPhotos] = createSignal([]);
539546
onMount(async () => {
540547
const res = await fetch(

src/rules/reactivity.ts

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -785,6 +785,7 @@ const rule: TSESLint.RuleModule<MessageIds, []> = {
785785
| T.VariableDeclarator
786786
| T.AssignmentExpression
787787
| T.TaggedTemplateExpression
788+
| T.NewExpression
788789
) => {
789790
const pushTrackedScope = (node: T.Node, expect: TrackedScope["expect"]) => {
790791
currentScope().trackedScopes.push({ node, expect });
@@ -848,7 +849,7 @@ const rule: TSESLint.RuleModule<MessageIds, []> = {
848849
} else if (node.type === "JSXSpreadAttribute") {
849850
// allow <div {...props.nestedProps} />; {...props} is already ignored
850851
pushTrackedScope(node.argument, "expression");
851-
} else if (node.type === "CallExpression") {
852+
} else if (node.type === "CallExpression" || node.type === "NewExpression") {
852853
if (node.callee.type === "Identifier") {
853854
const {
854855
callee,
@@ -878,16 +879,24 @@ const rule: TSESLint.RuleModule<MessageIds, []> = {
878879
} else if (
879880
matchImport(["onMount", "onCleanup", "onError"], callee.name) ||
880881
[
882+
// Timers
881883
"setInterval",
882884
"setTimeout",
883885
"setImmediate",
884886
"requestAnimationFrame",
885887
"requestIdleCallback",
888+
// Observers from Standard Web APIs
889+
"IntersectionObserver",
890+
"MutationObserver",
891+
"PerformanceObserver",
892+
"ReportingObserver",
893+
"ResizeObserver",
886894
].includes(callee.name)
887895
) {
888-
// on* and timers are NOT tracked scopes. However, they don't need to react
889-
// to updates to reactive variables; it's okay to poll the current
890-
// value. Consider them called-function tracked scopes for our purposes.
896+
// on*, timers, and observers are NOT tracked scopes. However, they
897+
// don't need to react to updates to reactive variables; it's okay
898+
// to poll the current value. Consider them called-function tracked
899+
// scopes for our purposes.
891900
pushTrackedScope(arg0, "called-function");
892901
} else if (matchImport("on", callee.name)) {
893902
// on accepts a signal or an array of signals as its first argument,
@@ -1072,6 +1081,9 @@ const rule: TSESLint.RuleModule<MessageIds, []> = {
10721081
checkForReactiveAssignment(null, node);
10731082
}
10741083
},
1084+
NewExpression(node: T.NewExpression) {
1085+
checkForTrackedScopes(node);
1086+
},
10751087
VariableDeclarator(node: T.VariableDeclarator) {
10761088
if (node.init) {
10771089
checkForReactiveAssignment(node.id, node.init);

test/rules/reactivity.test.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,6 @@ export const cases = run("reactivity", rule, {
3434
`let Component = () => {
3535
const [a, setA] = createSignal(1);
3636
const [b, setB] = createSignal(1);
37-
3837
createEffect(() => {
3938
console.log(a(), untrack(b));
4039
});
@@ -118,6 +117,13 @@ export const cases = run("reactivity", rule, {
118117
setImmediate(() => console.log(signal()));
119118
requestAnimationFrame(() => console.log(signal()));
120119
requestIdleCallback(() => console.log(signal()));`,
120+
// Observers from Standard Web APIs
121+
`const [signal] = createSignal(5);
122+
new IntersectionObserver(() => console.log(signal()));
123+
new MutationObserver(() => console.log(signal()));
124+
new PerformanceObserver(() => console.log(signal()));
125+
new ReportingObserver(() => console.log(signal()));
126+
new ResizeObserver(() => console.log(signal()));`,
121127
// Async tracking scope exceptions
122128
`const [photos, setPhotos] = createSignal([]);
123129
onMount(async () => {

0 commit comments

Comments
 (0)