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

feat: add overrides.namedExports to func-style rule #18444

Merged
merged 6 commits into from
May 17, 2024
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
97 changes: 96 additions & 1 deletion docs/src/rules/func-style.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,9 +59,14 @@ This rule has a string option:
* `"expression"` (default) requires the use of function expressions instead of function declarations
* `"declaration"` requires the use of function declarations instead of function expressions

This rule has an object option for an exception:
This rule has an object option for two exception:
kecrily marked this conversation as resolved.
Show resolved Hide resolved

* `"allowArrowFunctions"`: `true` (default `false`) allows the use of arrow functions. This option applies only when the string option is set to `"declaration"` (arrow functions are always allowed when the string option is set to `"expression"`, regardless of this option)
* `"overrides"`:
* `"namedExports": "expression" | "declaration" | "ignore"`: used to override function styles in named exports
* `"expression"`: like string option
* `"declaration"`: like string option
* `"ignore"`: either style is acceptable

### expression

Expand Down Expand Up @@ -148,6 +153,96 @@ var foo = () => {};

:::

### overrides

#### namedExports

##### expression

Examples of **incorrect** code for this rule with the `"declaration"` and `{"overrides": { "namedExports": "expression" }}` option:

::: incorrect

```js
/*eslint func-style: ["error", "declaration", { "overrides": { "namedExports": "expression" } }]*/

export function foo() {
// ...
}
```

:::

Examples of **correct** code for this rule with the `"declaration"` and `{"overrides": { "namedExports": "expression" }}` option:
kecrily marked this conversation as resolved.
Show resolved Hide resolved

::: correct

```js
/*eslint func-style: ["error", "declaration", { "overrides": { "namedExports": "expression" } }]*/

export var foo = function() {
// ...
};

export var bar = () => {};
```

:::

##### declaration
kecrily marked this conversation as resolved.
Show resolved Hide resolved

Examples of **incorrect** code for this rule with the `"expression"` and `{"overrides": { "namedExports": "declaration" }}` option:

::: incorrect

```js
/*eslint func-style: ["error", "expression", { "overrides": { "namedExports": "declaration" } }]*/

export var foo = function() {
// ...
};

export var bar = () => {};
```

:::

Examples of **correct** code for this rule with the `"expression"` and `{"overrides": { "namedExports": "declaration" }}` option:
kecrily marked this conversation as resolved.
Show resolved Hide resolved

::: correct

```js
/*eslint func-style: ["error", "expression", { "overrides": { "namedExports": "declaration" } }]*/

export function foo() {
// ...
}
```

:::

##### ignore

Examples of **correct** code for this rule with the `{"overrides": { "namedExports": "ignore" }}` option:

::: correct

```js
/*eslint func-style: ["error", "expression", { "overrides": { "namedExports": "ignore" } }]*/

export var foo = function() {
// ...
};

export var bar = () => {};

export function baz() {
// ...
}
```

:::

## When Not To Use It

If you want to allow developers to each decide how they want to write functions on their own, then you can disable this rule.
43 changes: 39 additions & 4 deletions lib/rules/func-style.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,15 @@ module.exports = {
allowArrowFunctions: {
type: "boolean",
default: false
},
overrides: {
type: "object",
properties: {
namedExports: {
enum: ["declaration", "expression", "ignore"]
}
},
additionalProperties: false
}
},
additionalProperties: false
Expand All @@ -46,13 +55,22 @@ module.exports = {
const style = context.options[0],
allowArrowFunctions = context.options[1] && context.options[1].allowArrowFunctions,
enforceDeclarations = (style === "declaration"),
exportFunctionStyle = context.options[1] && context.options[1].overrides && context.options[1].overrides.namedExports,
stack = [];

const nodesToCheck = {
FunctionDeclaration(node) {
stack.push(false);

if (!enforceDeclarations && node.parent.type !== "ExportDefaultDeclaration") {
if (
!enforceDeclarations &&
node.parent.type !== "ExportDefaultDeclaration" &&
typeof exportFunctionStyle === "undefined"
) {
context.report({ node, messageId: "expression" });
}
kecrily marked this conversation as resolved.
Show resolved Hide resolved

if (node.parent.type === "ExportNamedDeclaration" && exportFunctionStyle === "expression") {
context.report({ node, messageId: "expression" });
}
},
Expand All @@ -63,7 +81,18 @@ module.exports = {
FunctionExpression(node) {
stack.push(false);

if (enforceDeclarations && node.parent.type === "VariableDeclarator") {
if (
enforceDeclarations &&
node.parent.type === "VariableDeclarator" &&
typeof exportFunctionStyle === "undefined"
) {
context.report({ node: node.parent, messageId: "declaration" });
}
kecrily marked this conversation as resolved.
Show resolved Hide resolved

if (
node.parent.type === "VariableDeclarator" && node.parent.parent.parent.type === "ExportNamedDeclaration" &&
exportFunctionStyle === "declaration"
) {
context.report({ node: node.parent, messageId: "declaration" });
}
},
Expand All @@ -86,8 +115,14 @@ module.exports = {
nodesToCheck["ArrowFunctionExpression:exit"] = function(node) {
const hasThisExpr = stack.pop();

if (enforceDeclarations && !hasThisExpr && node.parent.type === "VariableDeclarator") {
context.report({ node: node.parent, messageId: "declaration" });
if (!hasThisExpr && node.parent.type === "VariableDeclarator") {
if (enforceDeclarations && typeof exportFunctionStyle === "undefined") {
context.report({ node: node.parent, messageId: "declaration" });
}
kecrily marked this conversation as resolved.
Show resolved Hide resolved

if (node.parent.parent.parent.type === "ExportNamedDeclaration" && exportFunctionStyle === "declaration") {
context.report({ node: node.parent, messageId: "declaration" });
}
}
};
}
Expand Down
164 changes: 164 additions & 0 deletions tests/lib/rules/func-style.js
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,74 @@ ruleTester.run("func-style", rule, {
code: "var foo = () => { function foo() { this; } };",
options: ["declaration", { allowArrowFunctions: true }],
languageOptions: { ecmaVersion: 6 }
},
{
code: "export function foo() {};",
options: ["declaration"]
},
{
code: "export function foo() {};",
options: ["expression", { overrides: { namedExports: "declaration" } }]
},
{
code: "export function foo() {};",
options: ["declaration", { overrides: { namedExports: "declaration" } }]
},
{
code: "export function foo() {};",
options: ["expression", { overrides: { namedExports: "ignore" } }]
},
{
code: "export function foo() {};",
options: ["declaration", { overrides: { namedExports: "ignore" } }]
},
{
code: "export var foo = function(){};",
options: ["expression"]
},
{
code: "export var foo = function(){};",
options: ["declaration", { overrides: { namedExports: "expression" } }]
},
{
code: "export var foo = function(){};",
options: ["expression", { overrides: { namedExports: "expression" } }]
},
{
code: "export var foo = function(){};",
options: ["declaration", { overrides: { namedExports: "ignore" } }]
},
{
code: "export var foo = function(){};",
options: ["expression", { overrides: { namedExports: "ignore" } }]
},
{
code: "export var foo = () => {};",
options: ["expression", { overrides: { namedExports: "expression" } }]
},
{
code: "export var foo = () => {};",
options: ["declaration", { overrides: { namedExports: "expression" } }]
},
{
code: "export var foo = () => {};",
options: ["declaration", { overrides: { namedExports: "ignore" } }]
},
{
code: "export var foo = () => {};",
options: ["expression", { overrides: { namedExports: "ignore" } }]
},
{
code: "export var foo = () => {};",
options: ["declaration", { allowArrowFunctions: true, overrides: { namedExports: "expression" } }]
},
{
code: "export var foo = () => {};",
options: ["expression", { allowArrowFunctions: true, overrides: { namedExports: "expression" } }]
},
{
code: "export var foo = () => {};",
options: ["declaration", { allowArrowFunctions: true, overrides: { namedExports: "ignore" } }]
}
],

Expand Down Expand Up @@ -126,6 +194,102 @@ ruleTester.run("func-style", rule, {
type: "FunctionDeclaration"
}
]
},
{
code: "export function foo(){}",
options: ["expression"],
errors: [
{
messageId: "expression",
type: "FunctionDeclaration"
}
]
},
{
code: "export function foo() {};",
options: ["declaration", { overrides: { namedExports: "expression" } }],
errors: [
{
messageId: "expression",
type: "FunctionDeclaration"
}
]
},
{
code: "export function foo() {};",
options: ["expression", { overrides: { namedExports: "expression" } }],
errors: [
{
messageId: "expression",
type: "FunctionDeclaration"
}
]
},
{
code: "export var foo = function(){};",
options: ["declaration"],
languageOptions: { ecmaVersion: 6 },
errors: [
{
messageId: "declaration",
type: "VariableDeclarator"
}
]
},
{
code: "export var foo = function(){};",
options: ["expression", { overrides: { namedExports: "declaration" } }],
languageOptions: { ecmaVersion: 6 },
errors: [
{
messageId: "declaration",
type: "VariableDeclarator"
}
]
},
{
code: "export var foo = function(){};",
options: ["declaration", { overrides: { namedExports: "declaration" } }],
languageOptions: { ecmaVersion: 6 },
errors: [
{
messageId: "declaration",
type: "VariableDeclarator"
}
]
},
{
code: "export var foo = () => {};",
options: ["declaration"],
languageOptions: { ecmaVersion: 6 },
errors: [
{
messageId: "declaration",
type: "VariableDeclarator"
}
]
},
{
code: "export var b = () => {};",
options: ["expression", { overrides: { namedExports: "declaration" } }],
languageOptions: { ecmaVersion: 6 },
errors: [
{
messageId: "declaration",
type: "VariableDeclarator"
}
]
},
{
code: "export var c = () => {};",
options: ["declaration", { overrides: { namedExports: "declaration" } }],
languageOptions: { ecmaVersion: 6 },
errors: [
{
messageId: "declaration",
type: "VariableDeclarator"
}
]
}
]
});