Skip to content

Commit

Permalink
✨ improve 'getFunctionNameWithKind'
Browse files Browse the repository at this point in the history
  • Loading branch information
mysticatea committed May 14, 2021
1 parent 05b8390 commit 46771e1
Show file tree
Hide file tree
Showing 3 changed files with 145 additions and 92 deletions.
45 changes: 28 additions & 17 deletions docs/api/ast-utils.md
Original file line number Diff line number Diff line change
Expand Up @@ -214,16 +214,19 @@ Get the name and kind of a given function node.
- `({ foo: function() {} })` ........................ `method 'foo'`
- `({ ['foo']: function() {} })` .................... `method 'foo'`
- `({ [foo]: function() {} })` ...................... `method`
- `({ [foo]: function() {} })` ...................... `method [foo]` if sourceCode is present.
- `({ foo() {} })` .................................. `method 'foo'`
- `({ foo: function* foo() {} })` ................... `generator method 'foo'`
- `({ foo: function*() {} })` ....................... `generator method 'foo'`
- `({ ['foo']: function*() {} })` ................... `generator method 'foo'`
- `({ [foo]: function*() {} })` ..................... `generator method`
- `({ [foo]: function*() {} })` ..................... `generator method [foo]` if sourceCode is present.
- `({ *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`
- `({ [foo]: async function() {} })` ................ `async method [foo]` if sourceCode is present.
- `({ async foo() {} })` ............................ `async method 'foo'`
- `({ get foo() {} })` .............................. `getter 'foo'`
- `({ set foo(a) {} })` ............................. `setter 'foo'`
Expand All @@ -235,31 +238,36 @@ Get the name and kind of a given function node.
- `class A { *['foo']() {} }` ....................... `generator method 'foo'`
- `class A { async ['foo']() {} }` .................. `async method 'foo'`
- `class A { [foo]() {} }` .......................... `method`
- `class A { [foo]() {} }` .......................... `method [foo]` if sourceCode is present.
- `class A { *[foo]() {} }` ......................... `generator method`
- `class A { *[foo]() {} }` ......................... `generator method [foo]` if sourceCode is present.
- `class A { async [foo]() {} }` .................... `async method`
- `class A { async [foo]() {} }` .................... `async method [foo]` if sourceCode is present.
- `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'"`
- `class A { #foo() {} }` ........................... `private method #foo`
- `class A { '#foo'() {} }` ......................... `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() {} }` .................... `static private method #foo`
- `class A { static *#foo() {} }` ................... `static private generator method #foo`
- `class A { static async #foo() {} }` .............. `static private async method #foo`
- `class A { static get #foo() {} }` ................ `static private getter #foo`
- `class A { static set #foo(a) {} }` ............... `static private setter #foo`
- `class A { '#foo' = function() {} }` .............. `method '#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() {} }` ......... `static private method #foo"`
- `class A { static #foo = function*() {} }` ........ `static private generator method #foo"`
- `class A { static #foo = async function() {} }` ... `static private async method #foo"`
```

</details>
Expand All @@ -269,6 +277,7 @@ Get the name and kind of a given function node.
Name | Type | Description
:-----|:-----|:------------
node | Node | The function node to get the name and kind. This should be any of `FunctionDeclaration`, `FunctionExpression`, and `ArrowFunctionExpression` node.
sourceCode | SourceCode | Optional. The source code object to get the text of computed property keys.

### Return value

Expand All @@ -282,12 +291,14 @@ const { getFunctionNameWithKind } = require("eslint-utils")
module.exports = {
meta: {},
create(context) {
const sourceCode = context.getSourceCode()
return {
FunctionDeclaration(node) {
context.report({
node,
message: "disallow this {{name}}!",
data: { name: getFunctionNameWithKind(node) }
data: { name: getFunctionNameWithKind(node, sourceCode) }
})
},
}
Expand Down
88 changes: 49 additions & 39 deletions src/get-function-name-with-kind.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,23 +3,27 @@ import { getPropertyName } from "./get-property-name"
/**
* Get the name and kind of the given function node.
* @param {ASTNode} node - The function node to get.
* @param {SourceCode} [sourceCode] The source code object to get the code of computed property keys.
* @returns {string} The name and kind of the function node.
*/
export function getFunctionNameWithKind(node) {
// eslint-disable-next-line complexity
export function getFunctionNameWithKind(node, sourceCode) {
const parent = node.parent
const tokens = []
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")
}
const isObjectMethod = parent.type === "Property" && parent.value === node
const isClassMethod =
parent.type === "MethodDefinition" && parent.value === node
const isClassFieldMethod =
parent.type === "PropertyDefinition" && parent.value === node

// Modifiers.
if (isClassMethod || isClassFieldMethod) {
if (parent.static) {
tokens.push("static")
}
if (parent.key.type === "PrivateIdentifier") {
tokens.push("private")
}
}
if (node.async) {
tokens.push("async")
Expand All @@ -28,9 +32,8 @@ export function getFunctionNameWithKind(node) {
tokens.push("generator")
}

if (node.type === "ArrowFunctionExpression") {
tokens.push("arrow", "function")
} else if (parent.type === "Property" || isFieldDefinition) {
// Kinds.
if (isObjectMethod || isClassMethod) {
if (parent.kind === "constructor") {
return "constructor"
}
Expand All @@ -41,38 +44,45 @@ export function getFunctionNameWithKind(node) {
} else {
tokens.push("method")
}
} else if (isClassFieldMethod) {
tokens.push("method")
} else {
if (node.type === "ArrowFunctionExpression") {
tokens.push("arrow")
}
tokens.push("function")
}

if (node.id) {
tokens.push(`'${node.id.name}'`)
} else {
const name = privateName || getPropertyName(parent)

if (name) {
tokens.push(`'${name}'`)
// Names.
if (isObjectMethod || isClassMethod || isClassFieldMethod) {
if (parent.key.type === "PrivateIdentifier") {
tokens.push(`#${parent.key.name}`)
} else {
const name = getPropertyName(parent)
if (name) {
tokens.push(`'${name}'`)
} else if (sourceCode) {
const keyText = sourceCode.getText(parent.key)
if (!keyText.includes("\n")) {
tokens.push(`[${keyText}]`)
}
}
}
}

if (
node.type === "ArrowFunctionExpression" ||
(node.type === "FunctionExpression" && node.id === null)
} else if (node.id) {
tokens.push(`'${node.id.name}'`)
} else if (
parent.type === "VariableDeclarator" &&
parent.id &&
parent.id.type === "Identifier"
) {
if (
parent.type === "VariableDeclarator" &&
parent.id &&
parent.id.type === "Identifier"
) {
tokens.push(`'${parent.id.name}'`)
}
if (
parent.type === "AssignmentExpression" &&
parent.left &&
parent.left.type === "Identifier"
) {
tokens.push(`'${parent.left.name}'`)
}
tokens.push(`'${parent.id.name}'`)
} else if (
(parent.type === "AssignmentExpression" ||
parent.type === "AssignmentPattern") &&
parent.left &&
parent.left.type === "Identifier"
) {
tokens.push(`'${parent.left.name}'`)
}

return tokens.join(" ")
Expand Down
104 changes: 68 additions & 36 deletions test/get-function-name-with-kind.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,17 +34,17 @@ describe("The 'getFunctionNameWithKind' function", () => {
"({ foo: function foo() {} })": "method 'foo'",
"({ foo: function() {} })": "method 'foo'",
"({ ['foo']: function() {} })": "method 'foo'",
"({ [foo]: function() {} })": "method",
"({ [foo]: function() {} })": "method [foo]",
"({ foo() {} })": "method 'foo'",
"({ foo: function* foo() {} })": "generator method 'foo'",
"({ foo: function*() {} })": "generator method 'foo'",
"({ ['foo']: function*() {} })": "generator method 'foo'",
"({ [foo]: function*() {} })": "generator method",
"({ [foo]: function*() {} })": "generator method [foo]",
"({ *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",
"({ [foo]: async function() {} })": "async method [foo]",
"({ async foo() {} })": "async method 'foo'",
"({ get foo() {} })": "getter 'foo'",
"({ set foo(a) {} })": "setter 'foo'",
Expand All @@ -55,9 +55,9 @@ describe("The 'getFunctionNameWithKind' function", () => {
"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 { [foo]() {} }": "method [foo]",
"class A { *[foo]() {} }": "generator method [foo]",
"class A { async [foo]() {} }": "async method [foo]",
"class A { get foo() {} }": "getter 'foo'",
"class A { set foo(a) {} }": "setter 'foo'",
"class A { static foo() {} }": "static method 'foo'",
Expand All @@ -69,64 +69,67 @@ describe("The 'getFunctionNameWithKind' function", () => {

semver.gte(eslint.CLIEngine.version, "7.0.0")
? {
"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 { #foo() {} }": "private method #foo",
"class A { '#foo'() {} }": "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() {} }": "static private method #foo",
"class A { static *#foo() {} }":
"private static generator method '#foo'",
"static private generator method #foo",
"class A { static async #foo() {} }":
"private static async method '#foo'",
"static private async method #foo",
"class A { static get #foo() {} }":
"private static getter '#foo'",
"static private getter #foo",
"class A { static set #foo(a) {} }":
"private static setter '#foo'",
"static private setter #foo",
"class A { foo = function() {} }": "method 'foo'",
"class A { foo = () => {} }": "arrow function 'foo'",
"class A { foo = () => {} }": "method 'foo'",
"class A { foo = function*() {} }": "generator method 'foo'",
"class A { foo = async function() {} }": "async method 'foo'",
"class A { ['foo'] = function() {} }": "method 'foo'",
"class A { ['foo'] = () => {} }": "arrow function 'foo'",
"class A { ['foo'] = () => {} }": "method 'foo'",
"class A { ['foo'] = function*() {} }":
"generator method 'foo'",
"class A { ['foo'] = async function() {} }":
"async method 'foo'",
"class A { [foo] = function() {} }": "method",
"class A { [foo] = () => {} }": "arrow function",
"class A { [foo] = function*() {} }": "generator method",
"class A { [foo] = async function() {} }": "async method",
"class A { [foo] = function() {} }": "method [foo]",
"class A { [foo] = () => {} }": "method [foo]",
"class A { [foo] = function*() {} }":
"generator method [foo]",
"class A { [foo] = async function() {} }":
"async method [foo]",
"class A { static foo = function() {} }":
"static method 'foo'",
"class A { static foo = () => {} }":
"static arrow function 'foo'",
"class A { static foo = () => {} }": "static method 'foo'",
"class A { static foo = function*() {} }":
"static generator method 'foo'",
"class A { static foo = async function() {} }":
"static async method 'foo'",
"class A { #foo = function() {} }": "private method '#foo'",
"class A { #foo = () => {} }":
"private arrow function '#foo'",
"class A { #foo = function() {} }": "private method #foo",
"class A { #foo = () => {} }": "private method #foo",
"class A { #foo = function*() {} }":
"private generator method '#foo'",
"private generator method #foo",
"class A { #foo = async function() {} }":
"private async method '#foo'",
"private async method #foo",
"class A { static #foo = function() {} }":
"private static method '#foo'",
"static private method #foo",
"class A { static #foo = () => {} }":
"private static arrow function '#foo'",
"static private method #foo",
"class A { static #foo = function*() {} }":
"private static generator method '#foo'",
"static private generator method #foo",
"class A { static #foo = async function() {} }":
"private static async method '#foo'",
"static private async method #foo",
}
: {}
)

for (const key of Object.keys(expectedResults)) {
it(`should return "${expectedResults[key]}" for "${key}".`, () => {
const expectedResult1 = expectedResults[key].replace(/\s+\[.+?\]/gu, "")
const expectedResult2 = expectedResults[key]

it(`should return "${expectedResult1}" for "${key}".`, () => {
const linter = new eslint.Linter()

let actualResult = null
Expand All @@ -149,7 +152,36 @@ describe("The 'getFunctionNameWithKind' function", () => {
0,
messages[0] && messages[0].message
)
assert.strictEqual(actualResult, expectedResults[key])
assert.strictEqual(actualResult, expectedResult1)
})

it(`should return "${expectedResult2}" for "${key}" if sourceCode is present.`, () => {
const linter = new eslint.Linter()

let actualResult = null
linter.defineRule("test", context => ({
":function"(node) {
actualResult = getFunctionNameWithKind(
node,
context.getSourceCode()
)
},
}))
const messages = linter.verify(key, {
rules: { test: "error" },
parserOptions: {
ecmaVersion: semver.gte(eslint.CLIEngine.version, "7.0.0")
? 2022
: 2018,
},
})

assert.strictEqual(
messages.length,
0,
messages[0] && messages[0].message
)
assert.strictEqual(actualResult, expectedResult2)
})
}
})

0 comments on commit 46771e1

Please sign in to comment.