📄 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.TypeElement
node: TSESTree.TSInterfaceDeclaration | TSESTree.TSTypeLiteral
tsThisTypes: TSESTree.TSThisType[] | null
- Return Type:
void
- Calls:
context.report
context.sourceCode .getText() .slice
context.sourceCode.getCommentsBefore
context.sourceCode.getCommentsAfter
text.slice
suggestion.endsWith
suggestion.slice
shouldWrapSuggestion
context.sourceCode .getText() .slice
comments .map(({ type, value }) => type === AST_TOKEN_TYPES.Line ?
//${value}\n:
/${value}/\n, ) .join
fixes.push
fixer.insertTextBefore
comments.forEach
fixer.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)