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

fix(eslint-plugin): [method-signature-style] don't auto-fix interfaces within namespaces #2678

Merged
merged 8 commits into from Oct 19, 2020
115 changes: 72 additions & 43 deletions packages/eslint-plugin/src/rules/method-signature-style.ts
Expand Up @@ -97,6 +97,17 @@ export default util.createRule<Options, MessageIds>({
return '';
}

function isNodeParentModuleDeclaration(node: any): boolean {
if (node.parent?.type === AST_NODE_TYPES.TSModuleDeclaration) {
return true;
}

if (node.parent?.type === AST_NODE_TYPES.Program) {
return false;
}
return isNodeParentModuleDeclaration(node.parent);
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can remove the any here with the catch-all Node type

Suggested change
function isNodeParentModuleDeclaration(node: any): boolean {
if (node.parent?.type === AST_NODE_TYPES.TSModuleDeclaration) {
return true;
}
if (node.parent?.type === AST_NODE_TYPES.Program) {
return false;
}
return isNodeParentModuleDeclaration(node.parent);
}
function isNodeParentModuleDeclaration(node: TSESTree.Node): boolean {
if (!node.parent) {
return false;
}
if (node.parent.type === AST_NODE_TYPES.TSModuleDeclaration) {
return true;
}
if (node.parent.type === AST_NODE_TYPES.Program) {
return false;
}
return isNodeParentModuleDeclaration(node.parent);
}


return {
TSMethodSignature(methodNode): void {
if (mode === 'method') {
Expand All @@ -112,61 +123,79 @@ export default util.createRule<Options, MessageIds>({
getMethodKey(element) === getMethodKey(methodNode),
)
: [];
const isParentModule = isNodeParentModuleDeclaration(methodNode);

if (duplicatedKeyMethodNodes.length > 0) {
if (isParentModule) {
context.report({
node: methodNode,
messageId: 'errorMethod',
});
} else {
context.report({
node: methodNode,
messageId: 'errorMethod',
*fix(fixer) {
const methodNodes = [
methodNode,
...duplicatedKeyMethodNodes,
].sort((a, b) => (a.range[0] < b.range[0] ? -1 : 1));
const typeString = methodNodes.reduce(
(str, node, idx, nodes) => {
const params = getMethodParams(node);
const returnType = getMethodReturnType(node);
return `${str}(${params} => ${returnType})${
idx !== nodes.length - 1 ? ' & ' : ''
}`;
},
'',
);
const key = getMethodKey(methodNode);
const delimiter = getDelimiter(methodNode);
yield fixer.replaceText(
methodNode,
`${key}: ${typeString}${delimiter}`,
);
for (const node of duplicatedKeyMethodNodes) {
const lastToken = sourceCode.getLastToken(node);
if (lastToken) {
const nextToken = sourceCode.getTokenAfter(lastToken);
if (nextToken) {
yield fixer.remove(node);
yield fixer.replaceTextRange(
[lastToken.range[1], nextToken.range[0]],
'',
);
}
}
}
},
});
}
return;
}

if (isParentModule) {
context.report({
node: methodNode,
messageId: 'errorMethod',
*fix(fixer) {
const methodNodes = [
methodNode,
...duplicatedKeyMethodNodes,
].sort((a, b) => (a.range[0] < b.range[0] ? -1 : 1));
const typeString = methodNodes.reduce((str, node, idx, nodes) => {
const params = getMethodParams(node);
const returnType = getMethodReturnType(node);
return `${str}(${params} => ${returnType})${
idx !== nodes.length - 1 ? ' & ' : ''
}`;
}, '');
});
} else {
context.report({
node: methodNode,
messageId: 'errorMethod',
fix: fixer => {
const key = getMethodKey(methodNode);
const params = getMethodParams(methodNode);
const returnType = getMethodReturnType(methodNode);
const delimiter = getDelimiter(methodNode);
yield fixer.replaceText(
return fixer.replaceText(
methodNode,
`${key}: ${typeString}${delimiter}`,
`${key}: ${params} => ${returnType}${delimiter}`,
);
for (const node of duplicatedKeyMethodNodes) {
const lastToken = sourceCode.getLastToken(node);
if (lastToken) {
const nextToken = sourceCode.getTokenAfter(lastToken);
if (nextToken) {
yield fixer.remove(node);
yield fixer.replaceTextRange(
[lastToken.range[1], nextToken.range[0]],
'',
);
}
}
}
},
});
return;
}

context.report({
node: methodNode,
messageId: 'errorMethod',
fix: fixer => {
const key = getMethodKey(methodNode);
const params = getMethodParams(methodNode);
const returnType = getMethodReturnType(methodNode);
const delimiter = getDelimiter(methodNode);
return fixer.replaceText(
methodNode,
`${key}: ${params} => ${returnType}${delimiter}`,
);
},
});
},
TSPropertySignature(propertyNode): void {
const typeNode = propertyNode.typeAnnotation?.typeAnnotation;
Expand Down
23 changes: 23 additions & 0 deletions packages/eslint-plugin/tests/rules/method-signature-style.test.ts
Expand Up @@ -348,5 +348,28 @@ interface Foo {
},
],
},
{
code: noFormat`
declare global {
namespace jest {
interface Matchers<R, T> {
// Add overloads specific to the DOM
toHaveProp<K extends keyof DomPropsOf<T>>(name: K, value?: DomPropsOf<T>[K]): R;
toHaveProps(props: Partial<DomPropsOf<T>>): R;
}
}
}
`,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

indent the example one line, and then you can use the fixer to auto-format the code block

Suggested change
code: noFormat`
declare global {
namespace jest {
interface Matchers<R, T> {
// Add overloads specific to the DOM
toHaveProp<K extends keyof DomPropsOf<T>>(name: K, value?: DomPropsOf<T>[K]): R;
toHaveProps(props: Partial<DomPropsOf<T>>): R;
}
}
}
`,
code: `
declare global {
namespace jest {
interface Matchers<R, T> {
// Add overloads specific to the DOM
toHaveProp<K extends keyof DomPropsOf<T>>(name: K, value?: DomPropsOf<T>[K]): R;
toHaveProps(props: Partial<DomPropsOf<T>>): R;
}
}
}
`,

errors: [
{
messageId: 'errorMethod',
line: 6,
},
{
messageId: 'errorMethod',
line: 7,
},
],
},
],
});