📄 prefer-function-type.ts¶
📊 Analysis Summary¶
| Metric | Count |
|---|---|
| 🔧 Functions | 3 |
| 📦 Imports | 5 |
| 📊 Variables & Constants | 16 |
📚 Table of Contents¶
🛠️ File Location:¶
📂 packages/eslint-plugin/src/rules/prefer-function-type.ts
📦 Imports¶
| Name | Source |
|---|---|
TSESLint |
@typescript-eslint/utils |
TSESTree |
@typescript-eslint/utils |
AST_NODE_TYPES |
@typescript-eslint/utils |
AST_TOKEN_TYPES |
@typescript-eslint/utils |
createRule |
../util |
Variables & Constants¶
| Name | Type | Kind | Value | Exported |
|---|---|---|---|---|
phrases |
{ readonly [x: number]: "Interface" | "Type literal"; } |
const | `{ | |
| [AST_NODE_TYPES.TSInterfaceDeclaration]: 'Interface', | ||||
| [AST_NODE_TYPES.TSTypeLiteral]: 'Type literal', | ||||
| } as const` | ✓ | |||
expr |
any |
const | node.extends[0].expression |
✗ |
fixable |
boolean |
const | node.parent.type === AST_NODE_TYPES.ExportDefaultDeclaration |
✗ |
fixes |
TSESLint.RuleFix[] |
const | [] |
✗ |
start |
any |
const | member.range[0] |
✗ |
colonPos |
number |
const | member.returnType!.range[0] - start |
✗ |
comments |
any[] |
const | `[ | |
| ...context.sourceCode.getCommentsBefore(member), | ||||
| ...context.sourceCode.getCommentsAfter(member), | ||||
| ]` | ✗ | |||
suggestion |
string |
let/var | ``${text.slice(0, colonPos)} =>${text.slice( | |
| colonPos + 1, | ||||
| )}`` | ✗ | |||
lastChar |
"" | ";" |
const | suggestion.endsWith(';') ? ';' : '' |
✗ |
isParentExported |
boolean |
const | node.parent.type === AST_NODE_TYPES.ExportNamedDeclaration |
✗ |
commentText |
string |
let/var | `comment.type === AST_TOKEN_TYPES.Line | |
? //${comment.value} |
||||
| : `/${comment.value}/`` | ✗ | |||
isCommentOnTheSameLine |
boolean |
const | comment.loc.start.line === member.loc.start.line |
✗ |
fixStart |
any |
const | node.range[0] |
✗ |
fix |
(fixer: TSESLint.RuleFixer) => TSESLint.RuleFix[] |
const | `fixable | |
| ? null | ||||
| : (fixer: TSESLint.RuleFixer): TSESLint.RuleFix[] => { | ||||
| const fixes: TSESLint.RuleFix[] = []; | ||||
| const start = member.range[0]; | ||||
| // https://github.com/microsoft/TypeScript/pull/56908 | ||||
| // eslint-disable-next-line @typescript-eslint/no-non-null-assertion | ||||
| const colonPos = member.returnType!.range[0] - start; | ||||
| const text = context.sourceCode | ||||
| .getText() | ||||
| .slice(start, member.range[1]); | ||||
| const comments = [ | ||||
| ...context.sourceCode.getCommentsBefore(member), | ||||
| ...context.sourceCode.getCommentsAfter(member), | ||||
| ]; | ||||
| let suggestion = `${text.slice(0, colonPos)} =>${text.slice( | ||||
| colonPos + 1, | ||||
| )}`; | ||||
| const lastChar = suggestion.endsWith(';') ? ';' : ''; | ||||
| if (lastChar) { | ||||
| suggestion = suggestion.slice(0, -1); | ||||
| } | ||||
| if (shouldWrapSuggestion(node.parent)) { | ||||
suggestion = (${suggestion}); |
||||
| } |
if (node.type === AST_NODE_TYPES.TSInterfaceDeclaration) {
if (node.typeParameters != null) {
suggestion = `type ${context.sourceCode
.getText()
.slice(
node.id.range[0],
node.typeParameters.range[1],
)} = ${suggestion}${lastChar}`;
} else {
suggestion = `type ${node.id.name} = ${suggestion}${lastChar}`;
}
}
const isParentExported =
node.parent.type === AST_NODE_TYPES.ExportNamedDeclaration;
if (
node.type === AST_NODE_TYPES.TSInterfaceDeclaration &&
isParentExported
) {
const commentsText = comments
.map(({ type, value }) =>
type === AST_TOKEN_TYPES.Line
? `//${value}\n`
: `/*${value}*/\n`,
)
.join('');
// comments should move before export and not between export and interface declaration
fixes.push(fixer.insertTextBefore(node.parent, commentsText));
} else {
comments.forEach(comment => {
let commentText =
comment.type === AST_TOKEN_TYPES.Line
? `//${comment.value}`
: `/*${comment.value}*/`;
const isCommentOnTheSameLine =
comment.loc.start.line === member.loc.start.line;
if (!isCommentOnTheSameLine) {
commentText += '\n';
} else {
commentText += ' ';
}
suggestion = commentText + suggestion;
});
}
const fixStart = node.range[0];
fixes.push(
fixer.replaceTextRange([fixStart, node.range[1]], suggestion),
);
return fixes;
}` | ✗ |
| tsThisTypes | TSESTree.TSThisType[] | null | let/var | null | ✗ |
| literalNesting | number | let/var | 0 | ✗ |
Functions¶
hasOneSupertype(node: TSESTree.TSInterfaceDeclaration): boolean¶
Code
-
JSDoc:
-
Parameters:
node: TSESTree.TSInterfaceDeclaration- Return Type:
boolean
shouldWrapSuggestion(parent: TSESTree.Node | undefined): boolean¶
Code
-
JSDoc:
-
Parameters:
parent: TSESTree.Node | undefined- Return Type:
boolean
checkMember(member: TSESTree.TypeElement, node: TSESTree.TSInterfaceDeclaration | TSESTree.TSTypeLiteral, tsThisTypes: TSESTree.TSThisType[] | null): void¶
Code
function checkMember(
member: TSESTree.TypeElement,
node: TSESTree.TSInterfaceDeclaration | TSESTree.TSTypeLiteral,
tsThisTypes: TSESTree.TSThisType[] | null = null,
): void {
if (
(member.type === AST_NODE_TYPES.TSCallSignatureDeclaration ||
member.type === AST_NODE_TYPES.TSConstructSignatureDeclaration) &&
member.returnType != null
) {
if (
tsThisTypes?.length &&
node.type === AST_NODE_TYPES.TSInterfaceDeclaration
) {
// the message can be confusing if we don't point directly to the `this` node instead of the whole member
// and in favour of generating at most one error we'll only report the first occurrence of `this` if there are multiple
context.report({
node: tsThisTypes[0],
messageId: 'unexpectedThisOnFunctionOnlyInterface',
data: {
interfaceName: node.id.name,
},
});
return;
}
const fixable =
node.parent.type === AST_NODE_TYPES.ExportDefaultDeclaration;
const fix = fixable
? null
: (fixer: TSESLint.RuleFixer): TSESLint.RuleFix[] => {
const fixes: TSESLint.RuleFix[] = [];
const start = member.range[0];
// https://github.com/microsoft/TypeScript/pull/56908
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const colonPos = member.returnType!.range[0] - start;
const text = context.sourceCode
.getText()
.slice(start, member.range[1]);
const comments = [
...context.sourceCode.getCommentsBefore(member),
...context.sourceCode.getCommentsAfter(member),
];
let suggestion = `${text.slice(0, colonPos)} =>${text.slice(
colonPos + 1,
)}`;
const lastChar = suggestion.endsWith(';') ? ';' : '';
if (lastChar) {
suggestion = suggestion.slice(0, -1);
}
if (shouldWrapSuggestion(node.parent)) {
suggestion = `(${suggestion})`;
}
if (node.type === AST_NODE_TYPES.TSInterfaceDeclaration) {
if (node.typeParameters != null) {
suggestion = `type ${context.sourceCode
.getText()
.slice(
node.id.range[0],
node.typeParameters.range[1],
)} = ${suggestion}${lastChar}`;
} else {
suggestion = `type ${node.id.name} = ${suggestion}${lastChar}`;
}
}
const isParentExported =
node.parent.type === AST_NODE_TYPES.ExportNamedDeclaration;
if (
node.type === AST_NODE_TYPES.TSInterfaceDeclaration &&
isParentExported
) {
const commentsText = comments
.map(({ type, value }) =>
type === AST_TOKEN_TYPES.Line
? `//${value}\n`
: `/*${value}*/\n`,
)
.join('');
// comments should move before export and not between export and interface declaration
fixes.push(fixer.insertTextBefore(node.parent, commentsText));
} else {
comments.forEach(comment => {
let commentText =
comment.type === AST_TOKEN_TYPES.Line
? `//${comment.value}`
: `/*${comment.value}*/`;
const isCommentOnTheSameLine =
comment.loc.start.line === member.loc.start.line;
if (!isCommentOnTheSameLine) {
commentText += '\n';
} else {
commentText += ' ';
}
suggestion = commentText + suggestion;
});
}
const fixStart = node.range[0];
fixes.push(
fixer.replaceTextRange([fixStart, node.range[1]], suggestion),
);
return fixes;
};
context.report({
node: member,
messageId: 'functionTypeOverCallableType',
data: {
literalOrInterface: phrases[node.type],
},
fix,
});
}
}
-
JSDoc:
-
Parameters:
member: TSESTree.TypeElementnode: TSESTree.TSInterfaceDeclaration | TSESTree.TSTypeLiteraltsThisTypes: TSESTree.TSThisType[] | null- Return Type:
void - Calls:
context.reportcontext.sourceCode .getText() .slicecontext.sourceCode.getCommentsBeforecontext.sourceCode.getCommentsAftertext.slicesuggestion.endsWithsuggestion.sliceshouldWrapSuggestioncontext.sourceCode .getText() .slicecomments .map(({ type, value }) => type === AST_TOKEN_TYPES.Line ?//${value}\n:/${value}/\n, ) .joinfixes.pushfixer.insertTextBeforecomments.forEachfixer.replaceTextRange- Internal Comments:
// the message can be confusing if we don't point directly to the `this` node instead of the whole member (x4) // and in favour of generating at most one error we'll only report the first occurrence of `this` if there are multiple (x4) // https://github.com/microsoft/TypeScript/pull/56908 (x2) // eslint-disable-next-line @typescript-eslint/no-non-null-assertion (x2) // comments should move before export and not between export and interface declaration (x4)