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

Open
wants to merge 67 commits into
base: main
Choose a base branch
from

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

const create = context => ({
Program(programNode) {
const scope = context.sourceCode.getScope(programNode);
for (const variable of scope.variables) {
Copy link
Contributor

Choose a reason for hiding this comment

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

scope.variables requires window/global/self to be defined in globals field in eslint config. If user who uses this rule and does not defined these 3 variables in globals field, this rule will not work. So scope.through should be included.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thanks for report, now it fixed and added test cases that are not declared in globals options

@param {import('estree').Identifier} node
*/
function reportProblem(context, node) {
if (isWindowSpecificAPI(node.parent) || isWebWorkerSpecificAPI(node.parent) || isComputedMemberExpression(node.parent, node)) {
Copy link
Contributor

Choose a reason for hiding this comment

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

If it's a computed member, I think eslint should report. For example,

const foo = 'some string'
window[foo] // Bad
self[foo] // Bad

@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.

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
6 participants