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

Feature request: Support snapshot testing #14936

Open
fisker opened this issue Aug 16, 2021 · 36 comments
Open

Feature request: Support snapshot testing #14936

fisker opened this issue Aug 16, 2021 · 36 comments
Assignees
Labels
core Relates to ESLint's core APIs and features enhancement This change enhances an existing feature of ESLint needs design Important details about this change need to be discussed

Comments

@fisker
Copy link
Contributor

fisker commented Aug 16, 2021

We test rules with a custom rule tester doing a snapshot test.

The built-in RuleTester can't customize the output assertion, we don't have the output to send to RuleTester, so we have to use SourceCodeFixer to apply fix from messages. Full implementation

We definitely want a better way to do snapshot test, maybe pass an assert option in future, but before that, can we expose SourceCodeFixer, so I can apply fix from the message?

The version of ESLint you are using.

8.0.0-beta.0

The problem you want to solve.

Fix our rule tester.

Are you willing to submit a pull request to implement this change?

Yes.

@fisker fisker 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 Aug 16, 2021
@nzakas
Copy link
Member

nzakas commented Aug 16, 2021

Can you provide an example of the type of test that you would like to write?

@fisker
Copy link
Contributor Author

fisker commented Aug 16, 2021

The tests

The snapshots

@nzakas
Copy link
Member

nzakas commented Aug 17, 2021

Sorry, what I meant was, can you show how you are currently using SourceCodeFixer? My preference is not to expose that as an API, but I’m curious if the pattern you were using is something we should just build into RuleTester. Please provide as much detail as possible to help us evaluate this.

@fisker
Copy link
Contributor Author

fisker commented Aug 17, 2021

Here is the line I use it, I've linked in the description.

Update: I'm sorry that I forgot to mention this is request to assert output for suggestions.

@nzakas
Copy link
Member

nzakas commented Aug 17, 2021

Ah! I understand now, thanks for explaining. So you are wanting to make sure that each suggestion, when applied, is outputting the correct result?

If so, that seems like something we should build into RuleTester to work the same way that we currently work for regular fixes. Would that address your use case?

@nzakas nzakas removed the triage An ESLint team member will look at this issue soon label Aug 17, 2021
@fisker
Copy link
Contributor Author

fisker commented Aug 18, 2021

So you are wanting to make sure that each suggestion, when applied, is outputting the correct result?

That's right.

Would that address your use case?

I'm afraid it won't.
There is another problem, I can't do snapshot testing with builtin RuleTester, the output (which I don't have) is required for invalid cases, see this part

if (hasOwnProperty(item, "output")) {
if (item.output === null) {
assert.strictEqual(
result.output,
item.code,
"Expected no autofixes to be suggested"
);
} else {
assert.strictEqual(result.output, item.output, "Output is incorrect.");
}
} else {
assert.strictEqual(
result.output,
item.code,
"The rule fixed the code. Please add 'output' property."
);
}

As mentioned in the issue description, unless we can customize the assertion step

maybe pass an assert option in future

@nzakas
Copy link
Member

nzakas commented Aug 18, 2021

Hmm, okay. Well, I still think the correct solution here is to figure out how to modify RuleTester to support your case. It would be helpful if you could give an overview of how your custom-built system works, exactly how snapshots are generated, and what parts of RuleTester you are already using (or not).

And please be as detailed as you can.

@nzakas
Copy link
Member

nzakas commented Aug 18, 2021

Note to self (mostly): You can currently test suggestions in RuleTester: https://eslint.org/docs/developer-guide/nodejs-api#testing-suggestions.

@lydell
Copy link

lydell commented Aug 20, 2021

I use a hack to be able to use snapshot testing in output. My tests look like this:

https://github.com/lydell/eslint-plugin-simple-import-sort/blob/1f753fb3e5b8eadf6cf9fdda8790d01dd9eade26/test/imports.test.js#L78-L90

And the hack looks like so:

https://github.com/lydell/eslint-plugin-simple-import-sort/blob/1f753fb3e5b8eadf6cf9fdda8790d01dd9eade26/test/helpers.js#L6-L22

There are some things very specific to that plugin, but the gist is that it replace assert.strictEqual with my own function, that tries to detect the RuleTester and do something else in that case.

@nzakas
Copy link
Member

nzakas commented Aug 21, 2021

@lydell thanks, that’s helpful. I think it’s clear people want snapshot testing, so I’ll ask you the same question: what would we need to add to RuleTester that would eliminate your hacks?

@lydell
Copy link

lydell commented Aug 21, 2021

This change would do it for me:

-output: string,
+output: string | ((actual: string) => void),

Currently, you can set output to the expected code as a string, and the RuleTester asserts that the actual fixed code and output are equal.

My proposal would be that you could optionally set output to a function instead. The RuleTester runs that function with the actual fixed code. It’s up to the function to assert that the passed string is the expected code in whatever way it wants.

With that, I could remove my hack and all I would have to do to my tests would be this (which would be totally fine):

    {
      code: input`
          |import x2 from "b"
          |import x1 from "a";
      `,
      output: (actual) => {
-        expect(actual).toMatchInlineSnapshot(`
+        expect(format(actual)).toMatchInlineSnapshot(`
          |import x1 from "a";
          |import x2 from "b"
        `);
      },
      errors: 1,
    },

(The format call there replaces the plugin specific parts of my current hack. As long as I have the hack it’s more convenient to have format baked in there, but it’s not a problem for me having to call format in each assertion. I always copy-paste a previous test when making a new one anyway, and it’d be obvious in the snapshot if I forget.)

@nzakas
Copy link
Member

nzakas commented Aug 24, 2021

Okay, so it sounds like what is needed is a way to use a custom assert to compare the actual output to the expected output. That’s seems like a reasonable addition.

@eslint/eslint-tsc this seems like something we should address before the final v8 is released.

@lydell
Copy link

lydell commented Aug 24, 2021

Okay, so it sounds like what is needed is a way to use a custom assert to compare the actual output to the expected output.

It’s a bit difficult for me to understand what this means. To me it sounds a bit like new RuleTester({ assert: myCustomAssertModule }). If that’s the case, it’s good to know that it wouldn’t work with Jest’s .toMatchInlineSnapshot(). Jest wouldn’t find a location in the code to write the snapshot of a particular test to.

@nzakas
Copy link
Member

nzakas commented Aug 24, 2021

So here's what I'm thinking:

{
    code: "foo;",
    output: "bar;",
    outputAssert(actual, expected) {
        // do what you want here
    }
}

Notes on this:

  • output is not required.
  • outputAssert defaults to what we are doing currently if not provided. If provided, we do not do the current assertion.
  • actual returns the result text from SourceCodeFixer
  • expected returns the value of output, which means if you omit output, then expected is undefined.
  • You don't need to use expected in outputAssert if you don't want to

So, you could do this:

{
    code: "foo;",
    output: "bar;",
    outputAssert: assert.strictEqual
}

Or you could do this:

{
    code: "foo;",
    outputAssert(actual) {
        expect(actual).toMatchInlineSnapshot("bar;");
    }
}

Or you could do this:

{
    code: "foo;",
    output: "bar;",
    outputAssert(actual, expected) {
        expect(actual).toMatchInlineSnapshot(expected);
    }
}

@lydell
Copy link

lydell commented Aug 24, 2021

Sounds good to me! (For the record, nobody would do your last example in practice, but it is possible.)

@mdjermanovic
Copy link
Member

Here is the line I use it, I've linked in the description.

Update: I'm sorry that I forgot to mention this is request to assert output for suggestions.

@fisker can you apply the fix manually?

It should be as simple as:

const { fix } = suggestion;
const output = `${code.slice(0, fix.range[0])}${fix.text}${code.slice(fix.range[1])}`;

Applying multiple fixes has additional logic due to possible overlapping, so that would be indeed complicated. For a single fix, it's just replacing the range with the given text, as described in EditInfo type, e.g., integrations are expected to manually apply suggestions.

@nzakas
Copy link
Member

nzakas commented Aug 25, 2021

@lydell @fisker I pulled together outputAssert for RuleTester on this PR:
#14978

Can you give the code a try and let me know if it works for your use cases?

@lydell
Copy link

lydell commented Aug 25, 2021

@nzakas Awesome! Do you have any tips on how to try your code? I attempted this:

  1. Change package.json to: "eslint": "eslint/eslint#00801f71cee455c83ba4ec3017c533319e37ed3b",
  2. Run npm install
  3. Run npx jest

I get this error:

 FAIL  test/imports.test.js
  ● Test suite failed to run

    TypeError: The argument 'filename' must be a file URL object, file URL string, or absolute path string. Received 'http://localhost/eslintrc.cjs'

      at Function.createRequire (node_modules/jest-runtime/build/index.js:1418:23)
      at Object.<anonymous> (node_modules/@eslint/eslintrc/lib/config-array-factory.js:57:17)
      at Object.<anonymous> (node_modules/eslint/lib/cli-engine/cli-engine.js:33:5)

The I tried upgrading Jest from v26 to v27, and got this error instead:

 FAIL  test/imports.test.js
  ● Test suite failed to run

    Cannot find module '@eslint/eslintrc/universal' from 'node_modules/eslint/lib/linter/linter.js'

    Require stack:
      node_modules/eslint/lib/linter/linter.js
      node_modules/eslint/lib/linter/index.js
      node_modules/eslint/lib/cli-engine/cli-engine.js
      node_modules/eslint/lib/eslint/eslint.js
      node_modules/eslint/lib/eslint/index.js
      node_modules/eslint/lib/api.js
      test/imports.test.js

      at Resolver.resolveModule (node_modules/jest-resolve/build/resolver.js:311:11)
      at Object.<anonymous> (node_modules/eslint/lib/linter/linter.js:27:9)

I’m a bit unsure if:

  • ESLint can be installed directly from GitHub?
  • If Jest is doing something bad that doesn’t work with ESLint 8?
  • If I’ve got some weird setup.
  • I’ve missed some ESLint 8 upgrade step?

@nzakas
Copy link
Member

nzakas commented Aug 26, 2021

Try removing the eslint package first and then reinstalling from the PR:

npm remove eslint —save-dev
npm install eslint/eslint#issue14936 —save-dev

@fisker
Copy link
Contributor Author

fisker commented Aug 26, 2021

Thanks for working on this.

I'm afraid it won't work for my case, as I understand, the outputAssert used to assert output and messages[index].suggestions[index].output, but I need assert message too.

As you can see in this snapshot, there is a code frame pointing the report range, this is the inital propose adding this custom tester, because it's hard to test the report range using the start/end.

How about pass all the output and messages(including suggestion.ouput)?

ruleTester.run("foo", replaceProgramWith5Rule, {
    valid: [],
    invalid: [
        {
            code: "var foo = bar;",
            output: "5",
			errors: 3,
            assert(actual, scenario) {
                assert.strictEqual(actual.output, scenario.output, 'Message');
                // Or maybe we can use `this`, but not important
                // assert.strictEqual(actual.output, this.output, 'Message');
                assert.strictEqual(actual.messages.length, scenario.errors, 'Message');
            },
        }
    ]
});

Also, @lydell need assert in each invalid scenario to use toMatchInlineSnapshot, but I don't need that, so can we also support put assertOutput on the root?

ruleTester.run("foo", replaceProgramWith5Rule, {
	assertOutput() {
		// ...
	},
    valid: [],
    invalid: [
        {
            code: "var foo = bar;",
            output: "5",
			errors: 3,
        }
    ]
});

@nzakas nzakas added the needs design Important details about this change need to be discussed label Aug 26, 2021
@mdjermanovic
Copy link
Member

@fisker we're trying to figure out if this is a blocker for v8.0.0.

Could you please try this solution: #14936 (comment) and tell us whether or not it works for your use case?

@mdjermanovic
Copy link
Member

https://github.com/sindresorhus/eslint-plugin-unicorn/blob/d51a197067b1d09e72de0ff6388ff8a09ebd3742/test/utils/snapshot-rule-tester.mjs#L187

- const {output} = SourceCodeFixer.applyFixes(code, [suggestion]);
+ const { fix } = suggestion;
+ const output = `${code.slice(0, fix.range[0])}${fix.text}${code.slice(fix.range[1])}`;

@mdjermanovic
Copy link
Member

I made a minimal gist for trying the RuleTester in ESLint 8 with Jest: https://gist.github.com/lydell/d62ce96c95c035811133a5396195da14

It doesn’t work. Probably Jest’s fault? Not sure what to do with this.

@lydell can you try this:

// ------- jest.config.js -------------------

module.exports = {
    "moduleNameMapper": {
        "@eslint/eslintrc/universal": "@eslint/eslintrc/dist/eslintrc-universal.cjs"
    }
}

I found this suggestion in jestjs/jest#11100

@fisker
Copy link
Contributor Author

fisker commented Aug 27, 2021

I've copied the whole SourceCodeFixer https://github.com/sindresorhus/eslint-plugin-unicorn/pull/1488/files#diff-342ff0788a2d0ad291c2f5630bef947fcf20017916cfb0e13c4aedf45b0b15b1R1, it works, but I can try your simple version when I back to my laptop.

@lydell
Copy link

lydell commented Aug 27, 2021

@mdjermanovic Thank you very much! Adding that to jest.config.js does the trick. I’ve updated the gist to mention this (in case someone finds it googling for my error).

Now that I can run my tests, I can report that … outputAssert from this PR works perfectly for my use case!

@nzakas
Copy link
Member

nzakas commented Aug 28, 2021

@lydell great! We are going to hold off until after v8.0.0 to look at this further.

@fisker I don’t think a single assert method is a good idea. Anyone using that would need to know all the different types of asserts RuleTester already does and remember to check them all. Another option might be to add separate methods, so outputAssert, errorCountAssert, errorAssert, etc.

In the near term I’m shifting focus back to v8.0.0, but if anyone else wants to put together an RFC for a change here, please feel free.

@fisker
Copy link
Contributor Author

fisker commented Sep 18, 2021

@mdjermanovic Your suggestion works as expected. fisker/eslint-plugin-unicorn@5e2c671

@mdjermanovic
Copy link
Member

@mdjermanovic Your suggestion works as expected. fisker/eslint-plugin-unicorn@5e2c671

Great, thanks for the info! Since that works, it seems that there are no blocking issues for v8.0.0 here.

@mdjermanovic mdjermanovic changed the title Feature request: Expose SourceCodeFixer Feature request: Support snapshot testing Sep 30, 2021
@nzakas nzakas linked a pull request Nov 2, 2021 that will close this issue
1 task
@github-actions
Copy link

Oops! It looks like we lost track of this issue. What do we want to do here? This issue will auto-close in 7 days without an update.

@github-actions github-actions bot added the Stale label Nov 29, 2021
@nzakas nzakas self-assigned this Nov 30, 2021
@nzakas
Copy link
Member

nzakas commented Nov 30, 2021

We still want to take a look at this and would be grateful if someone wants to put together an RFC.

@bmish
Copy link
Member

bmish commented Jan 2, 2022

FYI: I am hoping to work on this / write an RFC in the near future.

We have snapshot testing in a related linter I work on, ember-template-lint, which could be useful as prior art:

@axetroy
Copy link

axetroy commented Sep 7, 2024

Looks like this isn't planned yet.

However, node:test already supports snapshot testing experimentally.

I have implemented a SnapshotRuleTester based on this, I hope it can help to advance this feature or help someone who want a snapshot.

const assert = require('node:assert')
const { ESLint } = require('eslint')
const { codeFrameColumns } = require('@babel/code-frame')
const outdent = require('outdent')

const DEFAULT_SNAPSHOT = {
    assert: {
        snapshot: message => {
            assert.fail(message)
        }
    }
}

const DEFAULT_CONFIG = {
    languageOptions: {
        ecmaVersion: 'latest',
        parserOptions: {
            sourceType: 'module'
        }
    }
}

class TestRunner {
    /**
     *
     * @param {*} baseConfig
     * @param {import('node:test').TestContext} t
     */
    constructor(baseConfig = DEFAULT_CONFIG, t = DEFAULT_SNAPSHOT) {
        this.t = t
        this.baseConfig = baseConfig
    }

    /**
     *
     * @param {string} ruleName
     * @param {import('eslint').Rule.RuleModule} rule
     * @param {{
     *   valid: Array<string | import('eslint').RuleTester.ValidTestCase> | undefined,
     *   invalid: Array<string | import('eslint').RuleTester.InvalidTestCase> | undefined
     * }} tests
     * @returns {Promise<void>}
     */
    async run(ruleName, rule, tests) {
        const { valid = [], invalid = [] } = tests

        const failMessages = []
        const snapshot = []

        // Run valid test cases
        for (const [index, validCase] of valid.entries()) {
            const code = typeof validCase === 'string' ? validCase : validCase.code
            const options = typeof validCase === 'string' ? undefined : validCase.options

            const message = await this.runSingleTest(index, ruleName, rule, code, options)

            if (message) {
                failMessages.push(
                    outdent`
                      The following test case should have passed but failed.

                      ${outdent`${message}`}
                    `
                )
            }
        }

        // Run invalid test cases
        for (const [index, invalidCase] of invalid.entries()) {
            const code = typeof invalidCase === 'string' ? invalidCase : invalidCase.code
            const options = typeof invalidCase === 'string' ? undefined : invalidCase.options

            const message = await this.runSingleTest(index, ruleName, rule, code, options)

            if (message.length > 0) {
                snapshot.push(message)
            } else {
                failMessages.push(
                    outdent`
                      The following test case should have failed but passed.

                      ${wrapCodeFrame(code)}
                    `
                )
            }
        }

        if (failMessages.length > 0) {
            assert.fail(failMessages.join('\n\n'))
        } else {
            this.generateSnapshot(snapshot.join('\n\n'))
        }
    }

    /**
     *
     * @param {number} index
     * @param {string} ruleName
     * @param {import('eslint').Rule.RuleModule} rule
     * @param {string} code
     * @param {Array<Record<string, any>>} [options]
     * @returns {Promise<string>}
     */
    async runSingleTest(index, ruleName, rule, code, options = []) {
        const eslint = new ESLint({
            cache: false,
            overrideConfigFile: true,
            baseConfig: {
                ...this.baseConfig,
                rules: {
                    [`snapshot-tester/${ruleName}`]: ['error', ...options].filter(Boolean) // 动态注入规则
                }
            },
            plugins: {
                'snapshot-tester': { rules: { [ruleName]: rule } } // 自定义规则注入
            }
        })

        const results = await eslint.lintText(code)

        return results
            .map(result => this.generateOutput(index, code, result.messages))
            .filter(Boolean)
            .join('\n\n')
    }

    /**
     *
     * @param {number} index
     * @param {string} code
     * @param {Array<import('eslint').Linter.LintMessage>} messages
     * @returns {string | void}
     */
    generateOutput(index, code, messages) {
        if (messages.length === 0) return

        const title = removeNewlines(code)

        /** @type {Array<string>} */
        const output = [
            `## invalid(${index + 1}): ${title}`,
            indent(
                outdent`
                  > Input

                  ${indent(wrapCodeFrame(code), 4)}
                `,
                2
            )
        ]

        for (const [i, message] of messages.entries()) {
            output.push(...this.generateErrorMessage(i, messages.length, code, message))
        }

        return output.join('\n\n')
    }

    /**
     * Generate error message
     * @param {number} index
     * @param {number} totalLength
     * @param {string} code
     * @param {import('eslint').Linter.LintMessage} message
     * @returns {string[]}
     */
    generateErrorMessage(index, totalLength, code, message) {
        const { line, column, endLine, endColumn } = message
        const frame = codeFrameColumns(code, { start: { line, column }, end: { line: endLine, column: endColumn } })

        const output = [
            message.fix
                ? indent(
                      outdent`
                        > Output
  
                        ${indent(wrapCodeFrame(applySingleFix(code, message.fix)), 4)}
                      `,
                      2
                  )
                : null,
            indent(
                outdent`
                  > Error ${index + 1}/${totalLength}: ${message.message}

                  ${indent(frame, 4)}
                `,
                2
            )
        ].filter(Boolean)

        if (message.suggestions?.length) {
            output.push(
                indent(
                    outdent`
                      ${message.suggestions
                          .map((suggestion, i) => {
                              const fix = suggestion.fix

                              // Apply fix
                              const fixedCode = applySingleFix(code, fix)

                              return outdent`
                                > Suggestion ${i + 1}/${message.suggestions.length}: ${suggestion.desc}
                  
                                ${indent(wrapCodeFrame(fixedCode), 4)}
                              `
                          })
                          .join('\n\n')}
                    `,
                    2
                )
            )
        }

        return output
    }

    /**
     * Generate snapshot file
     * @param {string} message
     */
    generateSnapshot(message) {
        this.t?.assert.snapshot(message, {
            serializers: [value => value]
        })
    }
}

/**
 *
 * @param {string} str
 * @returns
 */
function removeNewlines(str) {
    return str.replace(/[\n\r]+/g, String.raw`\n`) // 将所有换行符删除
}

/**
 *
 * @param {string} code
 * @returns
 */
function wrapCodeFrame(code) {
    return codeFrameColumns(code, { start: { line: 0, column: 0 } }, { linesAbove: Number.MAX_VALUE, linesBelow: Number.MAX_VALUE })
}

/**
 *
 * @param {string} code
 * @param {import('eslint').Rule.Fix} fix
 */
function applySingleFix(code, fix) {
    return replaceStringWithSlice(code, fix.range[0], fix.range[1], fix.text)
}

/**
 *
 * @param {string} str
 * @param {number} start
 * @param {number} end
 * @param {string} replacement
 * @returns {string}
 */
function replaceStringWithSlice(str, start, end, replacement) {
    return str.slice(0, start) + replacement + str.slice(end)
}

/**
 *
 * @param {string} input
 * @param {number} count
 * @returns
 */
function indent(input, count) {
    const indentation = ' '.repeat(count)

    return input
        .split('\n')
        .map(line => `${indentation}${line}`)
        .join('\n')
}

module.exports = TestRunner

Then run the test case with the following command

node --test --experimental-test-snapshots --test-update-snapshots ./react-prefer-props.test.js
const test = require('node:test')

const outdent = require('outdent')
const SnapshotTester = require('../../test/SnapshotTester')

const rule = require('./react-prefer-props')

test('react-prefer-props', async t => {
    const ruleTester = new SnapshotTester(
        {
            languageOptions: {
                ecmaVersion: 'latest',
                parserOptions: {
                    sourceType: 'module'
                }
            }
        },
        t
    )

    await ruleTester.run('react-prefer-props', rule, {
        valid: [
            // Valid cases
            'function App(props) {}',
            'function App({}) {}',
            'function App(name) {}',
            'var App = () => {}',
            // not for async function
            'async function App(prop) {}',
            // not for generator function
            'function* App(prop) {}',
            // not for multiple parameters
            'function App(prop, other) {}',
            // not for no parameters
            'function App() {}'
        ],

        invalid: [
            // Invalid cases
            'function App(prop) {}',
            'const App = function (prop) {}',
            'const App = (prop) => {}',
            outdent`
              const App = (prop) => {
                console.log(prop)
              }

              console.log(prop)
            `
        ]
    })
})

And then it will generate snapshot file react-prefer-props.test.js.snapshot

exports[`react-prefer-props 1`] = `
## invalid(1): function App(prop) {}

  > Input
  
        1 | function App(prop) {}

  > Output
  
        1 | function App(props) {}

  > Error 1/1: React component's input variable should be named 'props' instead of 'prop'.
  
      > 1 | function App(prop) {}
          |              ^^^^

## invalid(2): const App = function (prop) {}

  > Input
  
        1 | const App = function (prop) {}

  > Output
  
        1 | const App = function (props) {}

  > Error 1/1: React component's input variable should be named 'props' instead of 'prop'.
  
      > 1 | const App = function (prop) {}
          |                       ^^^^

## invalid(3): const App = (prop) => {}

  > Input
  
        1 | const App = (prop) => {}

  > Output
  
        1 | const App = (props) => {}

  > Error 1/1: React component's input variable should be named 'props' instead of 'prop'.
  
      > 1 | const App = (prop) => {}
          |              ^^^^

## invalid(4): const App = (prop) => {\\n  console.log(prop)\\n}\\nconsole.log(prop)

  > Input
  
        1 | const App = (prop) => {
        2 |   console.log(prop)
        3 | }
        4 |
        5 | console.log(prop)

  > Output
  
        1 | const App = (props) => {
        2 |   console.log(props)
        3 | }
        4 |
        5 | console.log(prop)

  > Error 1/1: React component's input variable should be named 'props' instead of 'prop'.
  
      > 1 | const App = (prop) => {
          |              ^^^^
        2 |   console.log(prop)
        3 | }
        4 |
`;

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
core Relates to ESLint's core APIs and features enhancement This change enhances an existing feature of ESLint needs design Important details about this change need to be discussed
Projects
Status: Waiting for RFC
Development

Successfully merging a pull request may close this issue.

6 participants