Skip to content

Create eslint plugin#709

Draft
NullVoxPopuli wants to merge 6 commits intomainfrom
create-eslint-plugin
Draft

Create eslint plugin#709
NullVoxPopuli wants to merge 6 commits intomainfrom
create-eslint-plugin

Conversation

@NullVoxPopuli
Copy link
Copy Markdown
Owner

@NullVoxPopuli NullVoxPopuli commented Dec 17, 2022

Goal is to help folks out with issues like what is reported here: #707

Todo: Lints for

  • warn: short circuiting logic can prevent other data from ever being consumed

    if (foo && bar) { /* ... */ }

    in this situation, if both foo and bar are tracked, and foo is always false, the resource will never add bar to it's list of reactive dependencies

  • against: setting variables on this within a resource

    • docs and explanation
    • within trackedFunction
      class Demo {
        // ❌ bad
        /* ... */ = trackedFunction(() => {
          this.prop = 1;
          /* ... */
        });
      }
    • within resource()
      class Demo {
        // ❌ bad
        /* ... */ = resource(() => {
          this.prop = 1;
          /* ... */
        });
      }
    • within argument thunk (args to a resource blueprint/factory/from)
      class Demo {
        // ❌ bad
        /* ... */ = MyResource.from(() => {
          this.prop = 1;
          return [ ... ];
        });
        // ❌ bad
        /* ... */ = RemoteData(() => {
          this.prop = 1;
          return 'https://...';
        });
      }
  • against: setting tracked state after the state was created (unless inside an awaited IIFE)

    class Demo {
      // ❌ bad
      /* ... */ = resource(() => {
        let state = new TrackedObject();
        state.data = 2
        /* ... */
      });
    
      // ✅ good
      /* ... */ = resource(() => {
        let state = new TrackedObject({ data: 2 });
          
        /* ... */
      });
    
      // ✅ good
      /* ... */ = resource(() => {
        let state = new TrackedObject();
          
        (async () => {
          state.data = 2        
        })();
        /* ... */
      });
    
      }
  • autofix: if this is accessed after an await, suggested and fix with destroyable protection (if isDestroyed || isDestroying) -- see also: Propose new rule: no-unsafe-this-access ember-cli/eslint-plugin-ember#1421

 class Demo {
   // ❌ bad
   /* ... */ = resource(() => {
     (async () => {
       await Promise.resolve();
       let foo = this.foo;
     })();
     /* ... */
   });

   // ✅ good
   /* ... */ = resource(() => {
     (async () => {
       await Promise.resolve();
       if (isDestroying(this) || isDestroyed(this)) return;

       let foo = this.foo;
     })();        
     /* ... */
   });

Stretch goal lints

  • against: if a function call within a resource is within the same file, and if it sets properties on this
  • strict against: all resources defined outside of a component (to force encapsulation)

@changeset-bot
Copy link
Copy Markdown

changeset-bot Bot commented Dec 17, 2022

🦋 Changeset detected

Latest commit: 566d0e2

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
eslint-plugin-ember-resources Major

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@cloudflare-workers-and-pages
Copy link
Copy Markdown

cloudflare-workers-and-pages Bot commented Dec 17, 2022

Deploying with  Cloudflare Pages  Cloudflare Pages

Latest commit: 427c4a1
Status: ✅  Deploy successful!
Preview URL: https://8ebed39f.ember-resources.pages.dev
Branch Preview URL: https://create-eslint-plugin.ember-resources.pages.dev

View logs

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Dec 17, 2022

Estimated impact to a consuming app, depending on which bundle is imported

js min min + gzip min + brotli
/index.js 13.97 kB 3.33 kB 1.36 kB 1.2 kB
├── core/class-based/index.js 4.43 kB 1.88 kB 929 B 798 B
├── core/function-based/index.js 6.03 kB 552 B 269 B 213 B
└── core/use.js 2.91 kB 415 B 256 B 203 B
/util/cell.js 2.28 kB 917 B 434 B 366 B
/util/debounce.js 2.64 kB 771 B 409 B 340 B
/util/ember-concurrency.js 4.33 kB 1.53 kB 733 B 626 B
/util/function-resource.js 216 B 154 B 123 B 87 B
/util/function.js 4.82 kB 2.99 kB 1.05 kB 905 B
/util/helper.js 1.79 kB 303 B 218 B 177 B
/util/keep-latest.js 1.75 kB 512 B 296 B 235 B
/util/map.js 5.19 kB 2.13 kB 918 B 794 B
/util/remote-data.js 4.86 kB 1.75 kB 675 B 596 B

@lifeart
Copy link
Copy Markdown

lifeart commented Jan 23, 2025

@NullVoxPopuli, wondering if we could land MVP version of this plugins and iterate on it.

@sukima
Copy link
Copy Markdown
Contributor

sukima commented Jan 23, 2025

short circuiting logic can prevent other data from ever being consumed
if (foo && bar) { /* ... */ }
in this situation, if both foo and bar are tracked, and foo is always false, the resource will never add bar to it's list of reactive dependencies

If I understand this correctly it is the same for getters and if so I disagree with this rule. I've thought about it a lot and the thing is the reactivity to bar is a no-op anyway if foo is falsey. Thus it doesn't make much difference. As foo would have to react first before bar even came into the concerns of being consumed.

Basically, the notion that bar needs to be consumed (registered as reactive) only matters when foo becomes truthy. For example, foo = false, bar = we don't care, In this case we wait till foo = true causing the code to re-execute under another consumption context in which case the second round foo is truthy and bar becomes consumed then registering it as reactive.

The logic works out and the worry that bar has any effect on the re-calculation of the resource when foo is falsey is a mental model mismatch. I don't see how an eslint rule prevents any foot guns in this case.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants