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

Change Request: provide option to allow parsing specific ES3 reserved words #15327

Closed
1 task done
ljharb opened this issue Nov 17, 2021 · 8 comments · Fixed by eslint/espree#522 or #15387
Closed
1 task done
Labels
accepted There is consensus among the team that this change meets the criteria for inclusion archived due to age This issue has been archived; please open a new issue for any further discussion core Relates to ESLint's core APIs and features enhancement This change enhances an existing feature of ESLint
Projects

Comments

@ljharb
Copy link
Sponsor Contributor

ljharb commented Nov 17, 2021

ESLint version

v8.2.0

What problem do you want to solve?

#15017 was great in spirit - I agreed with it - but my agreement assumed that

ES3 code using reserved words will throw a runtime error

was the case. I can't speak for char, because I don't use that anywhere - but I do use .native and int as identifiers/property names, and these work perfectly fine in everyone's favorite ES3 engine, IE 6 (and presumably in many of the others). I use .native in the runtime code path of resolve, which has extensive usage, so I'm pretty confident it doesn't actually throw a runtime error anywhere that matters.

This parsing error means I can't upgrade to eslint 8 on resolve or es-abstract (so far; i've only tested a dozen or two out of my ~350 packages) without making an undesirable and unnecessary code change.

What do you think is the correct solution?

Add a parserOption that allows specific reserved words to parse as identifiers or property names anyways (it could apply to any ecmaVersion). This would be a generic escape hatch, that would allow anyone to lint code that works on what most implementations are: not precisely matching any arbitrary spec edition.

I tried adding parserOptions.allowReserved: true, but this doesn't seem to do it.

Participation

  • I am willing to submit a pull request for this change.

Additional comments

Sorry for not realizing this would be the consequence sooner :-)

@ljharb ljharb added core Relates to ESLint's core APIs and features enhancement This change enhances an existing feature of ESLint triage An ESLint team member will look at this issue soon labels Nov 17, 2021
@eslint-github-bot eslint-github-bot bot added this to Needs Triage in Triage Nov 17, 2021
@nzakas nzakas removed the triage An ESLint team member will look at this issue soon label Nov 18, 2021
@nzakas
Copy link
Member

nzakas commented Nov 18, 2021

I’m not sure we want to add an option that is only relevant when ecmaVersion is 3. That seems like a recipe for confusion.

Reverting to previous behavior is an option, but would be a breaking change and would also not fulfill the goal of the change: to provide a strict ES3 parser.

@ljharb since it seems like you are doing some regular IE6 testing, can you get us a list of reserved words that do throw an error?

@nzakas nzakas moved this from Needs Triage to Feedback Needed in Triage Nov 18, 2021
@nzakas nzakas added the tsc agenda This issue will be discussed by ESLint's TSC at the next meeting label Nov 18, 2021
@nzakas
Copy link
Member

nzakas commented Nov 18, 2021

TSC Summary:* In v8.0, we changed the behavior of ecmaVersion:3 to forbid reserved words. IE 6 actually allows some (or all) reserved words to be used. This request is to provide a looser ES3 variant for such purposes.

TSC Question: How do we want to address this?

@ljharb
Copy link
Sponsor Contributor Author

ljharb commented Nov 18, 2021

My thinking was that it would apply to every ecmaVersion, to ensure it was generic and wouldn’t need attending to in the future.

I’ll certainly check IE 6-8, and a few other ES3 browsers, and report back with a list.

As for the goal: what value is there In a strict ES3 parser if no accessible engine actually implements it? My hope would be for a strict “matches implementations” parser, for as many implementation tranches as were practical.

@ljharb
Copy link
Sponsor Contributor Author

ljharb commented Nov 20, 2021

I ran this jsbin: https://jsbin.com/zihosuh/edit?js,output populated with this info: https://mathiasbynens.be/notes/reserved-keywords via this link: https://output.jsbin.com/zihosuh and got these results:

IE 6 es2: int (var) es2: int (prop) es2: int (fn) es2: byte (var) es2: byte (prop) es2: byte (fn) es2: char (var) es2: char (prop) es2: char (fn) es2: goto (var) es2: goto (prop) es2: goto (fn) es2: long (var) es2: long (prop) es2: long (fn) es2: final (var) es2: final (prop) es2: final (fn) es2: float (var) es2: float (prop) es2: float (fn) es2: short (var) es2: short (prop) es2: short (fn) es2: double (var) es2: double (prop) es2: double (fn) es2: native (var) es2: native (prop) es2: native (fn) es2: public (var) es2: public (prop) es2: public (fn) es2: static (var) es2: static (prop) es2: static (fn) es2: throws (var) es2: throws (prop) es2: throws (fn) es2: boolean (var) es2: boolean (prop) es2: boolean (fn) es2: package (var) es2: package (prop) es2: package (fn) es2: private (var) es2: private (prop) es2: private (fn) es2: abstract (var) es2: abstract (prop) es2: abstract (fn) es2: volatile (var) es2: volatile (prop) es2: volatile (fn) es2: interface (var) es2: interface (prop) es2: interface (fn) es2: protected (var) es2: protected (prop) es2: protected (fn) es2: transient (var) es2: transient (prop) es2: transient (fn) es2: implements (var) es2: implements (prop) es2: implements (fn) es2: synchronized (var) es2: synchronized (prop) es2: synchronized (fn) es5: eval (var)
IE 7 es2: int (var) es2: int (prop) es2: int (fn) es2: byte (var) es2: byte (prop) es2: byte (fn) es2: char (var) es2: char (prop) es2: char (fn) es2: goto (var) es2: goto (prop) es2: goto (fn) es2: long (var) es2: long (prop) es2: long (fn) es2: final (var) es2: final (prop) es2: final (fn) es2: float (var) es2: float (prop) es2: float (fn) es2: short (var) es2: short (prop) es2: short (fn) es2: double (var) es2: double (prop) es2: double (fn) es2: native (var) es2: native (prop) es2: native (fn) es2: public (var) es2: public (prop) es2: public (fn) es2: static (var) es2: static (prop) es2: static (fn) es2: throws (var) es2: throws (prop) es2: throws (fn) es2: boolean (var) es2: boolean (prop) es2: boolean (fn) es2: package (var) es2: package (prop) es2: package (fn) es2: private (var) es2: private (prop) es2: private (fn) es2: abstract (var) es2: abstract (prop) es2: abstract (fn) es2: volatile (var) es2: volatile (prop) es2: volatile (fn) es2: interface (var) es2: interface (prop) es2: interface (fn) es2: protected (var) es2: protected (prop) es2: protected (fn) es2: transient (var) es2: transient (prop) es2: transient (fn) es2: implements (var) es2: implements (prop) es2: implements (fn) es2: synchronized (var) es2: synchronized (prop) es2: synchronized (fn) es5: eval (var)
IE 8 es2: int (var) es2: int (prop) es2: int (fn) es2: byte (var) es2: byte (prop) es2: byte (fn) es2: char (var) es2: char (prop) es2: char (fn) es2: goto (var) es2: goto (prop) es2: goto (fn) es2: long (var) es2: long (prop) es2: long (fn) es2: final (var) es2: final (prop) es2: final (fn) es2: float (var) es2: float (prop) es2: float (fn) es2: short (var) es2: short (prop) es2: short (fn) es2: double (var) es2: double (prop) es2: double (fn) es2: native (var) es2: native (prop) es2: native (fn) es2: public (var) es2: public (prop) es2: public (fn) es2: static (var) es2: static (prop) es2: static (fn) es2: throws (var) es2: throws (prop) es2: throws (fn) es2: boolean (var) es2: boolean (prop) es2: boolean (fn) es2: package (var) es2: package (prop) es2: package (fn) es2: private (var) es2: private (prop) es2: private (fn) es2: abstract (var) es2: abstract (prop) es2: abstract (fn) es2: volatile (var) es2: volatile (prop) es2: volatile (fn) es2: interface (var) es2: interface (prop) es2: interface (fn) es2: protected (var) es2: protected (prop) es2: protected (fn) es2: transient (var) es2: transient (prop) es2: transient (fn) es2: implements (var) es2: implements (prop) es2: implements (fn) es2: synchronized (var) es2: synchronized (prop) es2: synchronized (fn) es5: eval (var)
FF 3 es1: do (prop) es1: do (fn) es1: if (prop) es1: if (fn) es1: in (prop) es1: in (fn) es1: for (prop) es1: for (fn) es1: new (prop) es1: new (fn) es1: try (prop) es1: try (fn) es1: var (prop) es1: var (fn) es1: case (prop) es1: case (fn) es1: else (prop) es1: else (fn) es1: enum (var) es1: enum (prop) es1: enum (fn) es1: null (prop) es1: null (fn) es1: this (prop) es1: this (fn) es1: true (prop) es1: true (fn) es1: void (prop) es1: void (fn) es1: with (prop) es1: with (fn) es1: break (prop) es1: break (fn) es1: catch (prop) es1: catch (fn) es1: class (var) es1: class (prop) es1: class (fn) es1: const (prop) es1: const (fn) es1: false (prop) es1: false (fn) es1: super (var) es1: super (prop) es1: super (fn) es1: throw (prop) es1: throw (fn) es1: while (prop) es1: while (fn) es1: delete (prop) es1: delete (fn) es1: export (prop) es1: export (fn) es1: import (prop) es1: import (fn) es1: return (prop) es1: return (fn) es1: switch (prop) es1: switch (fn) es1: typeof (prop) es1: typeof (fn) es1: default (prop) es1: default (fn) es1: extends (var) es1: extends (prop) es1: extends (fn) es1: finally (prop) es1: finally (fn) es1: continue (prop) es1: continue (fn) es1: debugger (prop) es1: debugger (fn) es1: function (prop) es1: function (fn) es2: do (prop) es2: do (fn) es2: if (prop) es2: if (fn) es2: in (prop) es2: in (fn) es2: for (prop) es2: for (fn) es2: int (var) es2: int (prop) es2: int (fn) es2: new (prop) es2: new (fn) es2: try (prop) es2: try (fn) es2: var (prop) es2: var (fn) es2: byte (var) es2: byte (prop) es2: byte (fn) es2: case (prop) es2: case (fn) es2: char (var) es2: char (prop) es2: char (fn) es2: else (prop) es2: else (fn) es2: enum (var) es2: enum (prop) es2: enum (fn) es2: goto (var) es2: goto (prop) es2: goto (fn) es2: long (var) es2: long (prop) es2: long (fn) es2: null (prop) es2: null (fn) es2: this (prop) es2: this (fn) es2: true (prop) es2: true (fn) es2: void (prop) es2: void (fn) es2: with (prop) es2: with (fn) es2: break (prop) es2: break (fn) es2: catch (prop) es2: catch (fn) es2: class (var) es2: class (prop) es2: class (fn) es2: const (prop) es2: const (fn) es2: false (prop) es2: false (fn) es2: final (var) es2: final (prop) es2: final (fn) es2: float (var) es2: float (prop) es2: float (fn) es2: short (var) es2: short (prop) es2: short (fn) es2: super (var) es2: super (prop) es2: super (fn) es2: throw (prop) es2: throw (fn) es2: while (prop) es2: while (fn) es2: delete (prop) es2: delete (fn) es2: double (var) es2: double (prop) es2: double (fn) es2: export (prop) es2: export (fn) es2: import (prop) es2: import (fn) es2: native (var) es2: native (prop) es2: native (fn) es2: public (var) es2: public (prop) es2: public (fn) es2: return (prop) es2: return (fn) es2: static (var) es2: static (prop) es2: static (fn) es2: switch (prop) es2: switch (fn) es2: throws (var) es2: throws (prop) es2: throws (fn) es2: typeof (prop) es2: typeof (fn) es2: boolean (var) es2: boolean (prop) es2: boolean (fn) es2: default (prop) es2: default (fn) es2: extends (var) es2: extends (prop) es2: extends (fn) es2: finally (prop) es2: finally (fn) es2: package (var) es2: package (prop) es2: package (fn) es2: private (var) es2: private (prop) es2: private (fn) es2: abstract (var) es2: abstract (prop) es2: abstract (fn) es2: continue (prop) es2: continue (fn) es2: debugger (prop) es2: debugger (fn) es2: function (prop) es2: function (fn) es2: volatile (var) es2: volatile (prop) es2: volatile (fn) es2: interface (var) es2: interface (prop) es2: interface (fn) es2: protected (var) es2: protected (prop) es2: protected (fn) es2: transient (var) es2: transient (prop) es2: transient (fn) es2: implements (var) es2: implements (prop) es2: implements (fn) es2: instanceof (prop) es2: instanceof (fn) es2: synchronized (var) es2: synchronized (prop) es2: synchronized (fn) es5: do (prop) es5: do (fn) es5: if (prop) es5: if (fn) es5: in (prop) es5: in (fn) es5: for (prop) es5: for (fn) es5: new (prop) es5: new (fn) es5: try (prop) es5: try (fn) es5: var (prop) es5: var (fn) es5: case (prop) es5: case (fn) es5: else (prop) es5: else (fn) es5: enum (var) es5: enum (prop) es5: enum (fn) es5: eval (var)
Safari 4 es2: int (var) es2: int (prop) es2: int (fn) es2: byte (var) es2: byte (prop) es2: byte (fn) es2: char (var) es2: char (prop) es2: char (fn) es2: goto (var) es2: goto (prop) es2: goto (fn) es2: long (var) es2: long (prop) es2: long (fn) es2: final (var) es2: final (prop) es2: final (fn) es2: float (var) es2: float (prop) es2: float (fn) es2: short (var) es2: short (prop) es2: short (fn) es2: double (var) es2: double (prop) es2: double (fn) es2: native (var) es2: native (prop) es2: native (fn) es2: public (var) es2: public (prop) es2: public (fn) es2: static (var) es2: static (prop) es2: static (fn) es2: throws (var) es2: throws (prop) es2: throws (fn) es2: boolean (var) es2: boolean (prop) es2: boolean (fn) es2: package (var) es2: package (prop) es2: package (fn) es2: private (var) es2: private (prop) es2: private (fn) es2: abstract (var) es2: abstract (prop) es2: abstract (fn) es2: volatile (var) es2: volatile (prop) es2: volatile (fn) es2: interface (var) es2: interface (prop) es2: interface (fn) es2: protected (var) es2: protected (prop) es2: protected (fn) es2: transient (var) es2: transient (prop) es2: transient (fn) es2: implements (var) es2: implements (prop) es2: implements (fn) es2: synchronized (var) es2: synchronized (prop) es2: synchronized (fn) es5: eval (var)

In other words, all of these are things that are reserved, and should be failing, but actually do work - and I think it's reasonable for eslint to parse them. The spec is only fiction unless implementations follow it - when every implementation does something different from the spec, TC39 changes the spec to match reality.

@btmills btmills added accepted There is consensus among the team that this change meets the criteria for inclusion and removed tsc agenda This issue will be discussed by ESLint's TSC at the next meeting labels Nov 21, 2021
@btmills
Copy link
Member

btmills commented Nov 21, 2021

In the 2021-11-18 TSC meeting, we agreed to add an allowReserved option that can be used with ecmaVersion: 3. Espree will be responsible for checking that constraint.

@nzakas nzakas moved this from Feedback Needed to Ready to Implement in Triage Nov 23, 2021
@nzakas
Copy link
Member

nzakas commented Nov 23, 2021

Following up on @ljharb’s Discord message.

The changes actually need to happen in Espree. You’d want to check that if allowReserved is present that ecmaVersion is 3 in this function (otherwise throw error):
https://github.com/eslint/espree/blob/ed95791c0d9334383ddf59ae0d0968842801c171/lib/options.js#L90

Down below you can set the value of allowReserved:

https://github.com/eslint/espree/blob/ed95791c0d9334383ddf59ae0d0968842801c171/lib/options.js#L95

And a couple of tests here:

https://github.com/eslint/espree/blob/main/tests/lib/options.js

@nzakas
Copy link
Member

nzakas commented Nov 23, 2021

Oh and don’t forget to update the README with the new option.

@ljharb
Copy link
Sponsor Contributor Author

ljharb commented Nov 23, 2021

Thanks! Filed eslint/espree#522, please review :-)

Triage automation moved this from Ready to Implement to Complete Nov 25, 2021
nzakas pushed a commit to eslint/espree that referenced this issue Nov 25, 2021
mdjermanovic added a commit that referenced this issue Dec 3, 2021
mdjermanovic added a commit that referenced this issue Dec 4, 2021
* feat: add `allowReserved` parser option

Fixes #15327

* update package.json
@eslint-github-bot eslint-github-bot bot locked and limited conversation to collaborators May 25, 2022
@eslint-github-bot eslint-github-bot bot added the archived due to age This issue has been archived; please open a new issue for any further discussion label May 25, 2022
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
accepted There is consensus among the team that this change meets the criteria for inclusion archived due to age This issue has been archived; please open a new issue for any further discussion core Relates to ESLint's core APIs and features enhancement This change enhances an existing feature of ESLint
Projects
Archived in project
Triage
Complete
Development

Successfully merging a pull request may close this issue.

3 participants