Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add prefer-global-this rule #2410

Merged
merged 71 commits into from
Sep 29, 2024
Merged

Conversation

axetroy
Copy link
Contributor

@axetroy axetroy commented Jul 26, 2024

Close #978

@axetroy

This comment was marked as outdated.

@fisker
Copy link
Collaborator

fisker commented Jul 26, 2024

The current implementation uses the Identifier node for judgment.

However, this node appears in many cases, so it may cause misjudgment. Maybe this isn't a good way to do it.

We can use GlobalReferenceTracker or use ReferenceTracker directly from @eslint-community/eslint-utils

@axetroy

This comment was marked as outdated.

@axetroy
Copy link
Contributor Author

axetroy commented Jul 26, 2024

/cc @fisker @fregante

Copy link
Collaborator

@fregante fregante left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The rule should accept and pass all known window properties, so it likely needs to import a list from npm.

Pass

window.innerHeight
window.addEventListener()
window.name = 'Future'

https://developer.mozilla.org/en-US/docs/Web/API/Window

@axetroy

This comment was marked as resolved.

@sindresorhus
Copy link
Owner

I agree, actual window properties should stay being window. since it's weird to get, for example, the innerHeight from globalThis.

@sindresorhus
Copy link
Owner

@fregante I don't think it can be an automatic list as https://developer.mozilla.org/en-US/docs/Web/API/Window lists a lot of non-window-specific properties too. For example, window.crypto should be globalThis.crypto.

@fregante
Copy link
Collaborator

These codes will still work after changing to globalThis

Interestingly, that appears to be true. I was under the impression that globalThis was not exactly window in browsers. Firefox for example returns false for 'chrome' in window in extensions (or at least it used to)

I don't think it can be an automatic list

As "automatic" as a list manually written and published by someone else, following the same meaning, that's what I meant 😃

But yeah the list isn't huge so a copy-paste from that page and manual exclusion isn't a huge problem, at least to get this started.

@axetroy

This comment was marked as resolved.

@sindresorhus
Copy link
Owner

Do I need to maintain this list in plugin, or a separate npm package?

I would prefer to maintain it here. The list should not be too long. Most properties on window. are just globals.

@sindresorhus
Copy link
Owner

You can probably find the main ones here: https://html.spec.whatwg.org/multipage/nav-history-apis.html#the-window-object

@axetroy

This comment was marked as outdated.

docs/rules/prefer-global-this.md Outdated Show resolved Hide resolved
rules/prefer-global-this.js Outdated Show resolved Hide resolved
test/prefer-global-this.mjs Outdated Show resolved Hide resolved
docs/rules/prefer-global-this.md Outdated Show resolved Hide resolved
rules/prefer-global-this.js Show resolved Hide resolved
rules/prefer-global-this.js Show resolved Hide resolved
@fregante
Copy link
Collaborator

fregante commented Jul 29, 2024

I can help filter out some of these.

From a first look, it varies:

  • some are definitely not window properties, like the Screen constructor
  • some are generally used without window. so they could be with globalThis or with nothing at all
  • some are events that aren’t strictly related to the window, like onpaste (globalThis.onpaste might work just as well)

For the first two categories, they should either use globalThis or nothing at all (unless they’re used as globalThis.location?.href). So I wonder if instead of “prefer globalThis” this rule should just be “don’t use window”

edit: however this would make the logic more complicated, so never mind the rule name change suggestion.

@sindresorhus
Copy link
Owner

So I wonder if instead of “prefer globalThis” this rule should just be “don’t use window”

But it's "don't use window, or self, or global". I think "prefer globalThis" is easier to say.

@fregante
Copy link
Collaborator

fregante commented Jul 29, 2024

What I was suggesting is that you should still prefer nothing at all over globalThis in some cases (unless you're using ?.), like you wouldn't use new globalThis.Image() or globalThis.Array.from() or globalThis.globalThis. It can be its own rule though.

Edit: I opened #2419

rules/prefer-global-this.js Outdated Show resolved Hide resolved
rules/prefer-global-this.js Show resolved Hide resolved
docs/rules/prefer-global-this.md Outdated Show resolved Hide resolved
test/prefer-global-this.mjs Show resolved Hide resolved
rules/prefer-global-this.js Outdated Show resolved Hide resolved
@axetroy axetroy marked this pull request as draft August 1, 2024 01:24
rules/prefer-global-this.js Outdated Show resolved Hide resolved
Copy link
Collaborator

@fisker fisker left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please try to reuse existing utilities.

@sindresorhus
Copy link
Owner

Let us know when this is ready for another review. Make sure https://github.com/sindresorhus/eslint-plugin-unicorn/pull/2410/files#r1710020285 is resolved first.

@axetroy
Copy link
Contributor Author

axetroy commented Sep 9, 2024

Let us know when this is ready for another review. Make sure #2410 (files) is resolved first.

I think we should ignore them, or simply ignore all computed properties.

@sindresorhus This is already resolved. see the test case https://github.com/sindresorhus/eslint-plugin-unicorn/pull/2410/files#diff-560654447874168910d7716e1da47d1f8f03eebfc3f5eaa14f7977133d1d1882R57-R64

@axetroy axetroy requested a review from fisker September 10, 2024 01:12
@axetroy
Copy link
Contributor Author

axetroy commented Sep 10, 2024

@fisker Could you please review it again and let me know if there is anything that needs to change

@zanminkian
Copy link
Contributor

I did not reviewed this PR carefully. Inspired by the implementation of eslint built-in rule no-restricted-globals. I provide a simple demo to implement it.

{
  create: (context) => ({
    Program: (node) => {
      const banned = ["global", "self"];
      // Report variables declared elsewhere
      const scope = context.sourceCode.getScope(node);
      scope.variables.forEach((v) => {
        if (banned.includes(v.name)) {
          v.references.forEach((ref) => {
            context.report({node:ref.identifier, message: "xxxxxx"});
          });
        }
      });
      // Report variables not declared at all
      scope.through.forEach((ref) => {
        if (banned.includes(ref.identifier.name)) {
           context.report({node:ref.identifier, message: "xxxxxx"});
        }
      });
    },
  }),
}

@axetroy

This comment was marked as resolved.

@axetroy
Copy link
Contributor Author

axetroy commented Sep 10, 2024

I did not reviewed this PR carefully. Inspired by the implementation of eslint built-in rule no-restricted-globals. I provide a simple demo to implement it.

{
  create: (context) => ({
    Program: (node) => {
      const banned = ["global", "self"];
      // Report variables declared elsewhere
      const scope = context.sourceCode.getScope(node);
      scope.variables.forEach((v) => {
        if (banned.includes(v.name)) {
          v.references.forEach((ref) => {
            context.report({node:ref.identifier, message: "xxxxxx"});
          });
        }
      });
      // Report variables not declared at all
      scope.through.forEach((ref) => {
        if (banned.includes(ref.identifier.name)) {
           context.report({node:ref.identifier, message: "xxxxxx"});
        }
      });
    },
  }),
}

Nice, it works without change any test case and the code is more concise

@param {import('eslint').Rule.RuleContext} context
@param {import('estree').Identifier} node
*/
function reportProblem(context, node) {
Copy link
Contributor

@zanminkian zanminkian Sep 14, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In this function, we should completely ban self and global, as they a not standard.

As for the window, if the api only works on browser, window can be used. Otherwise, window should be banned too.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Worker APIs should use self

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should they? I think self was chosen because there's no window, but globalThis should still be preferred there I think.

  • Service workers introduction: 2014
  • globalThis introduction: 2019

I think using self in service workers is more of a historical artifact rather than a necessity. We can discuss/change this later though, I don't want to hold this PR any longer 🙏

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@fregante Can you open an issue?

Copy link
Collaborator

@fisker fisker left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great work, and sorry for the delay.

@sindresorhus sindresorhus merged commit 1558cbe into sindresorhus:main Sep 29, 2024
17 checks passed
@sindresorhus
Copy link
Owner

Nice work, @axetroy 👍

@charles4221
Copy link

Just upgraded my version of the plugin and started getting lint errors for this rule on code like this:

export function detectPrefersDarkMode() {
  return (
    typeof window !== 'undefined' &&
    window.matchMedia('(prefers-color-scheme: dark)').matches
  );
}

Shouldn't this type of usage be excluded from this rule since globalThis.matchMedia will crash with a TypeError: globalThis.matchMedia is not a function?

@fregante
Copy link
Collaborator

fregante commented Oct 28, 2024

It works for me. That function does exist in your browser. Are you sure that's not just your test env crashing?

You probably want to replace all that with: globalThis.matchMedia?.(etc).matches

@perrin4869
Copy link

So this rule is failing for me on this line:

https://github.com/dotcore64/react-stay-scrolled/blob/master/src%2Findex.js#L11

Since globalThis is defined in all environments, it can't be used to detect a browser environment or a different environment.

It would be cool if these edge cases could be handled

@fregante
Copy link
Collaborator

fregante commented Nov 1, 2024

@perrin4869 see #2468

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.

Rule proposal: prefer-global-this
8 participants