Skip to content

Commit c4a6b60

Browse files
authored
feat: add allowTypeAnnotation to func-style (#19754)
1 parent fd467bb commit c4a6b60

File tree

4 files changed

+127
-5
lines changed

4 files changed

+127
-5
lines changed

docs/src/rules/func-style.md

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ This rule has a string option:
6464
This rule has an object option for two exceptions:
6565

6666
* `"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)
67+
* `"allowTypeAnnotation"`: `true` (default `false`) allows the use of function expressions and arrow functions when the variable declaration has type annotation, regardless of the `allowArrowFunctions` option. This option applies only when the string option is set to `"declaration"`. (TypeScript only)
6768
* `"overrides"`:
6869
* `"namedExports": "expression" | "declaration" | "ignore"`: used to override function styles in named exports
6970
* `"expression"`: like string option
@@ -173,6 +174,24 @@ const foo = () => {};
173174

174175
:::
175176

177+
### allowTypeAnnotation
178+
179+
Examples of **correct** TypeScript code for this rule with the `"declaration", { "allowTypeAnnotation": true }` options:
180+
181+
::: correct
182+
183+
```ts
184+
/*eslint func-style: ["error", "declaration", { "allowTypeAnnotation": true }]*/
185+
186+
type Fn = () => undefined;
187+
188+
const foo: Fn = function() {};
189+
190+
const bar: Fn = () => {};
191+
```
192+
193+
:::
194+
176195
### overrides
177196

178197
#### namedExports

lib/rules/func-style.js

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,15 @@
1111
/** @type {import('../types').Rule.RuleModule} */
1212
module.exports = {
1313
meta: {
14+
dialects: ["javascript", "typescript"],
15+
language: "javascript",
1416
type: "suggestion",
1517

1618
defaultOptions: [
1719
"expression",
1820
{
1921
allowArrowFunctions: false,
22+
allowTypeAnnotation: false,
2023
overrides: {},
2124
},
2225
],
@@ -39,6 +42,9 @@ module.exports = {
3942
allowArrowFunctions: {
4043
type: "boolean",
4144
},
45+
allowTypeAnnotation: {
46+
type: "boolean",
47+
},
4248
overrides: {
4349
type: "object",
4450
properties: {
@@ -60,7 +66,8 @@ module.exports = {
6066
},
6167

6268
create(context) {
63-
const [style, { allowArrowFunctions, overrides }] = context.options;
69+
const [style, { allowArrowFunctions, allowTypeAnnotation, overrides }] =
70+
context.options;
6471
const enforceDeclarations = style === "declaration";
6572
const { namedExports: exportFunctionStyle } = overrides;
6673
const stack = [];
@@ -136,7 +143,8 @@ module.exports = {
136143
node.parent.type === "VariableDeclarator" &&
137144
(typeof exportFunctionStyle === "undefined" ||
138145
node.parent.parent.parent.type !==
139-
"ExportNamedDeclaration")
146+
"ExportNamedDeclaration") &&
147+
!(allowTypeAnnotation && node.parent.id.typeAnnotation)
140148
) {
141149
context.report({
142150
node: node.parent,
@@ -148,7 +156,8 @@ module.exports = {
148156
node.parent.type === "VariableDeclarator" &&
149157
node.parent.parent.parent.type ===
150158
"ExportNamedDeclaration" &&
151-
exportFunctionStyle === "declaration"
159+
exportFunctionStyle === "declaration" &&
160+
!(allowTypeAnnotation && node.parent.id.typeAnnotation)
152161
) {
153162
context.report({
154163
node: node.parent,
@@ -183,7 +192,8 @@ module.exports = {
183192
enforceDeclarations &&
184193
(typeof exportFunctionStyle === "undefined" ||
185194
node.parent.parent.parent.type !==
186-
"ExportNamedDeclaration")
195+
"ExportNamedDeclaration") &&
196+
!(allowTypeAnnotation && node.parent.id.typeAnnotation)
187197
) {
188198
context.report({
189199
node: node.parent,
@@ -194,7 +204,8 @@ module.exports = {
194204
if (
195205
node.parent.parent.parent.type ===
196206
"ExportNamedDeclaration" &&
197-
exportFunctionStyle === "declaration"
207+
exportFunctionStyle === "declaration" &&
208+
!(allowTypeAnnotation && node.parent.id.typeAnnotation)
198209
) {
199210
context.report({
200211
node: node.parent,

lib/types/rules.d.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -807,6 +807,10 @@ export interface ESLintRules extends Linter.RulesRecord {
807807
* @default false
808808
*/
809809
allowArrowFunctions: boolean;
810+
/**
811+
* @default false
812+
*/
813+
allowTypeAnnotation: boolean;
810814
overrides: {
811815
namedExports: "declaration" | "expression" | "ignore";
812816
};

tests/lib/rules/func-style.js

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -422,6 +422,16 @@ ruleTester.run("func-style", rule, {
422422
},
423423
],
424424
},
425+
{
426+
code: "const foo = function() {};",
427+
options: ["declaration", { allowTypeAnnotation: true }],
428+
errors: [
429+
{
430+
messageId: "declaration",
431+
type: "VariableDeclarator",
432+
},
433+
],
434+
},
425435
{
426436
code: "$1: function $2() { }",
427437
languageOptions: { sourceType: "script" },
@@ -432,6 +442,48 @@ ruleTester.run("func-style", rule, {
432442
},
433443
],
434444
},
445+
{
446+
code: "const foo = () => {};",
447+
options: ["declaration", { allowTypeAnnotation: true }],
448+
errors: [
449+
{
450+
messageId: "declaration",
451+
type: "VariableDeclarator",
452+
},
453+
],
454+
},
455+
{
456+
code: "export const foo = function() {};",
457+
options: [
458+
"expression",
459+
{
460+
allowTypeAnnotation: true,
461+
overrides: { namedExports: "declaration" },
462+
},
463+
],
464+
errors: [
465+
{
466+
messageId: "declaration",
467+
type: "VariableDeclarator",
468+
},
469+
],
470+
},
471+
{
472+
code: "export const foo = () => {};",
473+
options: [
474+
"expression",
475+
{
476+
allowTypeAnnotation: true,
477+
overrides: { namedExports: "declaration" },
478+
},
479+
],
480+
errors: [
481+
{
482+
messageId: "declaration",
483+
type: "VariableDeclarator",
484+
},
485+
],
486+
},
435487
{
436488
code: "if (foo) function bar() {}",
437489
languageOptions: { sourceType: "script" },
@@ -561,6 +613,42 @@ ruleTesterTypeScript.run("func-style", rule, {
561613
code: "export const foo: () => void = function(): void {};",
562614
options: ["expression", { overrides: { namedExports: "ignore" } }],
563615
},
616+
{
617+
code: "const expression: Fn = function () {}",
618+
options: ["declaration", { allowTypeAnnotation: true }],
619+
},
620+
{
621+
code: "const arrow: Fn = () => {}",
622+
options: ["declaration", { allowTypeAnnotation: true }],
623+
},
624+
{
625+
code: "export const expression: Fn = function () {}",
626+
options: ["declaration", { allowTypeAnnotation: true }],
627+
},
628+
{
629+
code: "export const arrow: Fn = () => {}",
630+
options: ["declaration", { allowTypeAnnotation: true }],
631+
},
632+
{
633+
code: "export const expression: Fn = function () {}",
634+
options: [
635+
"expression",
636+
{
637+
allowTypeAnnotation: true,
638+
overrides: { namedExports: "declaration" },
639+
},
640+
],
641+
},
642+
{
643+
code: "export const arrow: Fn = () => {}",
644+
options: [
645+
"expression",
646+
{
647+
allowTypeAnnotation: true,
648+
overrides: { namedExports: "declaration" },
649+
},
650+
],
651+
},
564652
{
565653
code: "$1: function $2(): void { }",
566654
options: ["declaration"],

0 commit comments

Comments
 (0)