Skip to content

Commit

Permalink
✨ supports for ES2022 class features (#20)
Browse files Browse the repository at this point in the history
  • Loading branch information
ota-meshi authored and mysticatea committed May 14, 2021
1 parent 03506f0 commit 05b8390
Show file tree
Hide file tree
Showing 12 changed files with 536 additions and 188 deletions.
167 changes: 114 additions & 53 deletions docs/api/ast-utils.md
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,50 @@ Get the proper location of a given function node to report.
^^^^^^^^^^^^^^
- `class A { static set foo(a) {} }`
^^^^^^^^^^^^^^
- `class A { #foo() {} }`
^^^^
- `class A { *#foo() {} }`
^^^^^
- `class A { async #foo() {} }`
^^^^^^^^^^
- `class A { get #foo() {} }`
^^^^^^^^
- `class A { set #foo(a) {} }`
^^^^^^^^
- `class A { static #foo() {} }`
^^^^^^^^^^^
- `class A { static *#foo() {} }`
^^^^^^^^^^^^
- `class A { static async #foo() {} }`
^^^^^^^^^^^^^^^^^
- `class A { static get #foo() {} }`
^^^^^^^^^^^^^^^
- `class A { static set #foo(a) {} }`
^^^^^^^^^^^^^^^
- `class A { foo = function() {} }`
^^^^^^^^^^^^^^
- `class A { foo = function*() {} }`
^^^^^^^^^^^^^^^
- `class A { foo = async function() {} }`
^^^^^^^^^^^^^^^^^^^^
- `class A { static foo = function() {} }`
^^^^^^^^^^^^^^^^^^^^^
- `class A { static foo = function*() {} }`
^^^^^^^^^^^^^^^^^^^^^^
- `class A { static foo = async function() {} }`
^^^^^^^^^^^^^^^^^^^^^^^^^^^
- `class A { #foo = function() {} }`
^^^^^^^^^^^^^^^
- `class A { #foo = function*() {} }`
^^^^^^^^^^^^^^^^
- `class A { #foo = async function() {} }`
^^^^^^^^^^^^^^^^^^^^^
- `class A { static #foo = function() {} }`
^^^^^^^^^^^^^^^^^^^^^^
- `class A { static #foo = function*() {} }`
^^^^^^^^^^^^^^^^^^^^^^^
- `class A { static #foo = async function() {} }`
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
```

</details>
Expand Down Expand Up @@ -148,58 +192,74 @@ Get the name and kind of a given function node.
<details><summary>Show the name and kind examples:</summary>

```
- `function foo() {}` .................... `function 'foo'`
- `(function foo() {})` .................. `function 'foo'`
- `(function() {})` ...................... `function`
- `function* foo() {}` ................... `generator function 'foo'`
- `(function* foo() {})` ................. `generator function 'foo'`
- `(function*() {})` ..................... `generator function`
- `() => {}` ............................. `arrow function`
- `async () => {}` ....................... `async arrow function`
- `const foo = () => {}` ................. `arrow function 'foo'`
- `const foo = async () => {}` ........... `async arrow function 'foo'`
- `foo = () => {}` ....................... `arrow function 'foo'`
- `foo = async () => {}` ................. `async arrow function 'foo'`
- `const foo = function() {}` ............ `function 'foo'`
- `const foo = function* () {}` .......... `generator function 'foo'`
- `const foo = async function() {}` ...... `async function 'foo'`
- `foo = function() {}` .................. `function 'foo'`
- `foo = function* () {}` ................ `generator function 'foo'`
- `foo = async function() {}` ............ `async function 'foo'`
- `({ foo: function foo() {} })` ......... `method 'foo'`
- `({ foo: function() {} })` ............. `method 'foo'`
- `({ ['foo']: function() {} })` ......... `method 'foo'`
- `({ [foo]: function() {} })` ........... `method`
- `({ foo() {} })` ....................... `method 'foo'`
- `({ foo: function* foo() {} })` ........ `generator method 'foo'`
- `({ foo: function*() {} })` ............ `generator method 'foo'`
- `({ ['foo']: function*() {} })` ........ `generator method 'foo'`
- `({ [foo]: function*() {} })` .......... `generator method`
- `({ *foo() {} })` ...................... `generator method 'foo'`
- `({ foo: async function foo() {} })` ... `async method 'foo'`
- `({ foo: async function() {} })` ....... `async method 'foo'`
- `({ ['foo']: async function() {} })` ... `async method 'foo'`
- `({ [foo]: async function() {} })` ..... `async method`
- `({ async foo() {} })` ................. `async method 'foo'`
- `({ get foo() {} })` ................... `getter 'foo'`
- `({ set foo(a) {} })` .................. `setter 'foo'`
- `class A { constructor() {} }` ......... `constructor`
- `class A { foo() {} }` ................. `method 'foo'`
- `class A { *foo() {} }` ................ `generator method 'foo'`
- `class A { async foo() {} }` ........... `async method 'foo'`
- `class A { ['foo']() {} }` ............. `method 'foo'`
- `class A { *['foo']() {} }` ............ `generator method 'foo'`
- `class A { async ['foo']() {} }` ....... `async method 'foo'`
- `class A { [foo]() {} }` ............... `method`
- `class A { *[foo]() {} }` .............. `generator method`
- `class A { async [foo]() {} }` ......... `async method`
- `class A { get foo() {} }` ............. `getter 'foo'`
- `class A { set foo(a) {} }` ............ `setter 'foo'`
- `class A { static foo() {} }` .......... `static method 'foo'`
- `class A { static *foo() {} }` ......... `static generator method 'foo'`
- `class A { static async foo() {} }` .... `static async method 'foo'`
- `class A { static get foo() {} }` ...... `static getter 'foo'`
- `class A { static set foo(a) {} }` ..... `static setter 'foo'`
- `function foo() {}` ............................... `function 'foo'`
- `(function foo() {})` ............................. `function 'foo'`
- `(function() {})` ................................. `function`
- `function* foo() {}` .............................. `generator function 'foo'`
- `(function* foo() {})` ............................ `generator function 'foo'`
- `(function*() {})` ................................ `generator function`
- `() => {}` ........................................ `arrow function`
- `async () => {}` .................................. `async arrow function`
- `const foo = () => {}` ............................ `arrow function 'foo'`
- `const foo = async () => {}` ...................... `async arrow function 'foo'`
- `foo = () => {}` .................................. `arrow function 'foo'`
- `foo = async () => {}` ............................ `async arrow function 'foo'`
- `const foo = function() {}` ....................... `function 'foo'`
- `const foo = function* () {}` ..................... `generator function 'foo'`
- `const foo = async function() {}` ................. `async function 'foo'`
- `foo = function() {}` ............................. `function 'foo'`
- `foo = function* () {}` ........................... `generator function 'foo'`
- `foo = async function() {}` ....................... `async function 'foo'`
- `({ foo: function foo() {} })` .................... `method 'foo'`
- `({ foo: function() {} })` ........................ `method 'foo'`
- `({ ['foo']: function() {} })` .................... `method 'foo'`
- `({ [foo]: function() {} })` ...................... `method`
- `({ foo() {} })` .................................. `method 'foo'`
- `({ foo: function* foo() {} })` ................... `generator method 'foo'`
- `({ foo: function*() {} })` ....................... `generator method 'foo'`
- `({ ['foo']: function*() {} })` ................... `generator method 'foo'`
- `({ [foo]: function*() {} })` ..................... `generator method`
- `({ *foo() {} })` ................................. `generator method 'foo'`
- `({ foo: async function foo() {} })` .............. `async method 'foo'`
- `({ foo: async function() {} })` .................. `async method 'foo'`
- `({ ['foo']: async function() {} })` .............. `async method 'foo'`
- `({ [foo]: async function() {} })` ................ `async method`
- `({ async foo() {} })` ............................ `async method 'foo'`
- `({ get foo() {} })` .............................. `getter 'foo'`
- `({ set foo(a) {} })` ............................. `setter 'foo'`
- `class A { constructor() {} }` .................... `constructor`
- `class A { foo() {} }` ............................ `method 'foo'`
- `class A { *foo() {} }` ........................... `generator method 'foo'`
- `class A { async foo() {} }` ...................... `async method 'foo'`
- `class A { ['foo']() {} }` ........................ `method 'foo'`
- `class A { *['foo']() {} }` ....................... `generator method 'foo'`
- `class A { async ['foo']() {} }` .................. `async method 'foo'`
- `class A { [foo]() {} }` .......................... `method`
- `class A { *[foo]() {} }` ......................... `generator method`
- `class A { async [foo]() {} }` .................... `async method`
- `class A { get foo() {} }` ........................ `getter 'foo'`
- `class A { set foo(a) {} }` ....................... `setter 'foo'`
- `class A { static foo() {} }` ..................... `static method 'foo'`
- `class A { static *foo() {} }` .................... `static generator method 'foo'`
- `class A { static async foo() {} }` ............... `static async method 'foo'`
- `class A { static get foo() {} }` ................. `static getter 'foo'`
- `class A { static set foo(a) {} }` ................ `static setter 'foo'`
- `class A { #foo() {} }` ........................... `private method '#foo'`
- `class A { *#foo() {} }` .......................... `private generator method '#foo'`
- `class A { async #foo() {} }` ..................... `private async method '#foo'`
- `class A { get #foo() {} }` ....................... `private getter '#foo'`
- `class A { set #foo(a) {} }` ...................... `private setter '#foo'`
- `class A { static #foo() {} }` .................... `private static method '#foo'`
- `class A { static *#foo() {} }` ................... `private static generator method '#foo'`
- `class A { static async #foo() {} }` .............. `private static async method '#foo'`
- `class A { static get #foo() {} }` ................ `private static getter '#foo'`
- `class A { static set #foo(a) {} }` ............... `private static setter '#foo'`
- `class A { #foo = function() {} }` ................ `private method '#foo'"`
- `class A { #foo = function*() {} }` ............... `private generator method '#foo'"`
- `class A { #foo = async function() {} }` .......... `private async method '#foo'"`
- `class A { static #foo = function() {} }` ......... `private static method '#foo'"`
- `class A { static #foo = function*() {} }` ........ `private static generator method '#foo'"`
- `class A { static #foo = async function() {} }` ... `private static async method '#foo'"`
```

</details>
Expand Down Expand Up @@ -252,7 +312,7 @@ If the node is a computed property, this tries to compute the property name by t

Name | Type | Description
:-----|:-----|:------------
node | Node | The node to get that name. This shuld be any of `MemberExpression`, `Property`, and `MethodDefinition` node.
node | Node | The node to get that name. This shuld be any of `MemberExpression`, `Property`, `MethodDefinition`, and `PropertyDefinition` node.
initialScope | Scope or undefined | Optional. The scope object to find variables.

### Return value
Expand Down Expand Up @@ -385,6 +445,7 @@ The side effect means that it *may* modify a certain variable or object member.
- `MemberExpression` (`[computed = true]`)
- `MethodDefinition` (`[computed = true]`)
- `Property` (`[computed = true]`)
- `PropertyDefinition` (`[computed = true]`)
- `UnaryExpression` (`[operator = "-" | "+" | "!" | "~"]`)

### Parameters
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
"dot-prop": "^4.2.0",
"eslint": "^7.24.0",
"esm": "^3.2.25",
"espree": "^7.3.1",
"espree": "git+https://github.com/eslint/espree.git#class-fields",
"mocha": "^6.2.2",
"npm-run-all": "^4.1.5",
"nyc": "^14.1.1",
Expand Down
3 changes: 2 additions & 1 deletion src/get-function-head-location.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,8 @@ export function getFunctionHeadLocation(node, sourceCode) {
end = arrowToken.loc.end
} else if (
parent.type === "Property" ||
parent.type === "MethodDefinition"
parent.type === "MethodDefinition" ||
parent.type === "PropertyDefinition"
) {
start = parent.loc.start
end = getOpeningParenOfParams(node, sourceCode).loc.start
Expand Down
22 changes: 14 additions & 8 deletions src/get-function-name-with-kind.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,18 @@ import { getPropertyName } from "./get-property-name"
export function getFunctionNameWithKind(node) {
const parent = node.parent
const tokens = []

if (parent.type === "MethodDefinition" && parent.static) {
tokens.push("static")
const isFieldDefinition =
parent.type === "MethodDefinition" ||
parent.type === "PropertyDefinition"
let privateName = null
if (isFieldDefinition) {
if (parent.key.type === "PrivateIdentifier") {
privateName = `#${parent.key.name}`
tokens.push("private")
}
if (parent.static) {
tokens.push("static")
}
}
if (node.async) {
tokens.push("async")
Expand All @@ -21,10 +30,7 @@ export function getFunctionNameWithKind(node) {

if (node.type === "ArrowFunctionExpression") {
tokens.push("arrow", "function")
} else if (
parent.type === "Property" ||
parent.type === "MethodDefinition"
) {
} else if (parent.type === "Property" || isFieldDefinition) {
if (parent.kind === "constructor") {
return "constructor"
}
Expand All @@ -42,7 +48,7 @@ export function getFunctionNameWithKind(node) {
if (node.id) {
tokens.push(`'${node.id.name}'`)
} else {
const name = getPropertyName(parent)
const name = privateName || getPropertyName(parent)

if (name) {
tokens.push(`'${name}'`)
Expand Down
7 changes: 7 additions & 0 deletions src/get-property-name.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,16 +12,23 @@ export function getPropertyName(node, initialScope) {
if (node.computed) {
return getStringIfConstant(node.property, initialScope)
}
if (node.property.type === "PrivateIdentifier") {
return null
}
return node.property.name

case "Property":
case "MethodDefinition":
case "PropertyDefinition":
if (node.computed) {
return getStringIfConstant(node.key, initialScope)
}
if (node.key.type === "Literal") {
return String(node.key.value)
}
if (node.key.type === "PrivateIdentifier") {
return null
}
return node.key.name

// no default
Expand Down
37 changes: 28 additions & 9 deletions src/get-static-value.js
Original file line number Diff line number Diff line change
Expand Up @@ -249,6 +249,9 @@ const operations = Object.freeze({

if (args != null) {
if (calleeNode.type === "MemberExpression") {
if (calleeNode.property.type === "PrivateIdentifier") {
return null
}
const object = getStaticValueR(calleeNode.object, initialScope)
if (object != null) {
if (
Expand All @@ -257,9 +260,10 @@ const operations = Object.freeze({
) {
return { value: undefined, optional: true }
}
const property = calleeNode.computed
? getStaticValueR(calleeNode.property, initialScope)
: { value: calleeNode.property.name }
const property = getStaticPropertyNameValue(
calleeNode,
initialScope
)

if (property != null) {
const receiver = object.value
Expand Down Expand Up @@ -366,14 +370,15 @@ const operations = Object.freeze({
},

MemberExpression(node, initialScope) {
if (node.property.type === "PrivateIdentifier") {
return null
}
const object = getStaticValueR(node.object, initialScope)
if (object != null) {
if (object.value == null && (object.optional || node.optional)) {
return { value: undefined, optional: true }
}
const property = node.computed
? getStaticValueR(node.property, initialScope)
: { value: node.property.name }
const property = getStaticPropertyNameValue(node, initialScope)

if (property != null && !isGetter(object.value, property.value)) {
return { value: object.value[property.value] }
Expand Down Expand Up @@ -412,9 +417,10 @@ const operations = Object.freeze({
if (propertyNode.kind !== "init") {
return null
}
const key = propertyNode.computed
? getStaticValueR(propertyNode.key, initialScope)
: { value: propertyNode.key.name }
const key = getStaticPropertyNameValue(
propertyNode,
initialScope
)
const value = getStaticValueR(propertyNode.value, initialScope)
if (key == null || value == null) {
return null
Expand Down Expand Up @@ -522,6 +528,19 @@ function getStaticValueR(node, initialScope) {
return null
}

/**
* Get the static value of property name from a MemberExpression node or a Property node.
* @param {Node} node The node to get.
* @param {Scope} [initialScope] The scope to start finding variable. Optional. If the node is a computed property node and this scope was given, this checks the computed property name by the `getStringIfConstant` function with the scope, and returns the value of it.
* @returns {{value:any}|{value:undefined,optional?:true}|null} The static value of the property name of the node, or `null`.
*/
function getStaticPropertyNameValue(node, initialScope) {
const propertyKey = node.type === "Property" ? node.key : node.property
return node.computed
? getStaticValueR(propertyKey, initialScope)
: { value: propertyKey.name }
}

/**
* Get the value of a given node if it's a static value.
* @param {Node} node The node to get.
Expand Down
10 changes: 10 additions & 0 deletions src/has-side-effect.js
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,16 @@ const visitor = Object.freeze(
}
return this.$visitChildren(node, options, visitorKeys)
},
PropertyDefinition(node, options, visitorKeys) {
if (
options.considerImplicitTypeConversion &&
node.computed &&
node.key.type !== "Literal"
) {
return true
}
return this.$visitChildren(node, options, visitorKeys)
},
UnaryExpression(node, options, visitorKeys) {
if (node.operator === "delete") {
return true
Expand Down
Loading

0 comments on commit 05b8390

Please sign in to comment.