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(eslint-plugin): new rule method-signature-style #1685

Merged
merged 10 commits into from Apr 3, 2020
Merged

feat(eslint-plugin): new rule method-signature-style #1685

merged 10 commits into from Apr 3, 2020

Conversation

phaux
Copy link
Contributor

@phaux phaux commented Mar 6, 2020

Closes #1598

@typescript-eslint
Copy link
Contributor

Thanks for the PR, @phaux!

typescript-eslint is a 100% community driven project, and we are incredibly grateful that you are contributing to that community.

The core maintainers work on this in their personal time, so please understand that it may not be possible for them to review your work immediately.

Thanks again!


🙏 Please, if you or your company is finding typescript-eslint valuable, help us sustain the project by sponsoring it transparently on https://opencollective.com/typescript-eslint. As a thank you, your profile/company logo will be added to our main README which receives thousands of unique visitors per day.

@bradzacher bradzacher added the enhancement: new plugin rule New rule request for eslint-plugin label Mar 6, 2020
Copy link
Member

@bradzacher bradzacher left a comment

Choose a reason for hiding this comment

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

haven't reviewed the code, but a few quick comments on the documentation

```

A method and a function property of the same type behave differently.
Methods are always bivariant in their argument, while function properties are contravariant in their argument under `strictFunctionTypes`. See the reasoning behind that [here](https://github.com/microsoft/TypeScript/pull/18654).
Copy link
Member

Choose a reason for hiding this comment

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

Would be good to clarify the functionality with/without the flag, and better define bivariance and contravariance, as most users wouldn't know what those mean (I always forget and have to google it 😅).


With strictFunctionTypes turned off, both method signatures and function types are considered bivariant in their argument types (meaning that all arguments can be the same, supertypes, or subtypes).
However, with strictFunctionTypes turned on, method signatures remain bivariant, but function types become contravariant in their argument types (meaning that all arguments must be the same type, or a supertype).
See the reasoning behind that in the typescript PR for the compiler option.

This difference creates a possible safety hole when using method types instead of function types.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I didn't want to overwhelm the user with too much information. I changed this part to be more concise and only nudge the user towards using the more correct option. I think your explanation goes too much into detail. Tell me what you think.

That original paragraph was from TypeScript's FAQ. If the user wants to learn more they still can click the link.

@phaux phaux requested a review from bradzacher March 20, 2020 16:49
Copy link
Member

@bradzacher bradzacher left a comment

Choose a reason for hiding this comment

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

mostly LGTM right now.

One change for the fixer.
Thanks!

Comment on lines 54 to 64
function getMethodParams(
node: TSESTree.TSMethodSignature | TSESTree.TSFunctionType,
): string {
let params = node.params.map(node => sourceCode.getText(node)).join(', ');
params = `(${params})`;
if (node.typeParameters != null) {
const typeParams = sourceCode.getText(node.typeParameters);
params = `${typeParams}${params}`;
}
return params;
}
Copy link
Member

Choose a reason for hiding this comment

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

This will remove all comments inside the type.
For the most part I don't have problems with a fixer removing comments in weird places if it makes the fixer simpler and easier to maintain.

But one place we should accept and support comments is around parameters, eg:

method(/* leading comment */arg: string): void

So I would suggest converting this to something like:

  • grab first open paren token
  • grab last end paren token
  • grab source code between the two

That way we preserve the "expected" comments

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I just did that, but it only works if there's at least one argument. If the argument list is (/* comment */) then the comment is lost. The problem is that there's no way to find the paren tokens because the method node's "params" property is just an empty array. This is contrary to "typeParams" which is a separate AST node so I can just getText it even if it's empty.

@bradzacher bradzacher added the awaiting response Issues waiting for a reply from the OP or another party label Mar 23, 2020
@bradzacher bradzacher removed the awaiting response Issues waiting for a reply from the OP or another party label Mar 23, 2020
bradzacher
bradzacher previously approved these changes Apr 3, 2020
Copy link
Member

@bradzacher bradzacher left a comment

Choose a reason for hiding this comment

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

LGTM - thanks for doing this

@codecov
Copy link

codecov bot commented Apr 3, 2020

Codecov Report

Merging #1685 into master will decrease coverage by 0.02%.
The diff coverage is 89.47%.

@@            Coverage Diff             @@
##           master    #1685      +/-   ##
==========================================
- Coverage   94.73%   94.71%   -0.03%     
==========================================
  Files         160      161       +1     
  Lines        7300     7338      +38     
  Branches     2092     2101       +9     
==========================================
+ Hits         6916     6950      +34     
- Misses        164      165       +1     
- Partials      220      223       +3     
Flag Coverage Δ
#unittest 94.71% <89.47%> (-0.03%) ⬇️
Impacted Files Coverage Δ
.../eslint-plugin/src/rules/method-signature-style.ts 89.47% <89.47%> (ø)

@bradzacher bradzacher added 1 approval PR that a maintainer has LGTM'd - any maintainer can merge this when ready awaiting response Issues waiting for a reply from the OP or another party labels Apr 3, 2020
@bradzacher
Copy link
Member

Note - there was a linting change recently which has caused CI failures for your PR.
Please have a look and I can merge it afterward! Thanks

@phaux
Copy link
Contributor Author

phaux commented Apr 3, 2020

The problem is that the rule @typescript-eslint/internal/plugin-test-formatting crashes ESLint. It has something to do with a comment in interface Test { 'f!': </* a */>(/* b */ x: any /* c */) => void } in tests.

$ eslint . --ext .js,.ts
Error: Comment "a" was not printed. Please report this error!
Occurred while linting /home/runner/work/typescript-eslint/typescript-eslint/packages/eslint-plugin/tests/rules/method-signature-style.test.ts:10
    at /home/runner/work/typescript-eslint/typescript-eslint/node_modules/prettier/index.js:13223:13
    at Array.forEach (<anonymous>)
    at ensureAllCommentsPrinted (/home/runner/work/typescript-eslint/typescript-eslint/node_modules/prettier/index.js:13221:15)
    at coreFormat (/home/runner/work/typescript-eslint/typescript-eslint/node_modules/prettier/index.js:13270:3)
    at format (/home/runner/work/typescript-eslint/typescript-eslint/node_modules/prettier/index.js:13510:73)
    at formatWithCursor (/home/runner/work/typescript-eslint/typescript-eslint/node_modules/prettier/index.js:13526:12)
    at /home/runner/work/typescript-eslint/typescript-eslint/node_modules/prettier/index.js:44207:15
    at Object.format (/home/runner/work/typescript-eslint/typescript-eslint/node_modules/prettier/index.js:44226:12)
    at prettierFormat (/home/runner/work/typescript-eslint/typescript-eslint/packages/eslint-plugin-internal/dist/rules/plugin-test-formatting.js:120:35)
    at checkTemplateLiteral (/home/runner/work/typescript-eslint/typescript-eslint/packages/eslint-plugin-internal/dist/rules/plugin-test-formatting.js:284:31)
error Command failed with exit code 2.

@bradzacher
Copy link
Member

bradzacher commented Apr 3, 2020

Ahh fooey. This is an unhandled case in prettier that's fixed in v2.0, but we haven't upgraded to v2 yet (https://prettier.io/blog/2020/03/21/2.0.0.html#fix-printing-of-comments-in-empty-type-parameters-7729httpsgithubcomprettierprettierpull7729-by-sosukesuzukihttpsgithubcomsosukesuzuki)

You can get around this for now by adding the noFormat template tag to that test. This will instruct the lint rule to not run prettier on that test snippet.

example:

code: noFormat`
function foo() { return (1 as any); }
function foo() { return Object.create(null); }
const foo = () => { return (1 as any) };
const foo = () => Object.create(null);
`,

@bradzacher bradzacher removed the awaiting response Issues waiting for a reply from the OP or another party label Apr 3, 2020
Copy link
Member

@bradzacher bradzacher left a comment

Choose a reason for hiding this comment

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

thanks!

@bradzacher bradzacher merged commit c49d771 into typescript-eslint:master Apr 3, 2020
@boris-petrov
Copy link

Thanks for this nice check but how are we supposed to handle overloaded methods? I.e.:

interface T {
  func: (arg: string) => number;
  func: (arg1: number, arg2: object) => number;
}

This is not valid (duplicate key error by TypeScript).

@bradzacher
Copy link
Member

bradzacher commented Apr 6, 2020

You can do it via an intersection type:

interface T {
  func:
    & ((arg: string) => number)
    & ((arg1: number, arg2: object) => number);
}

repl

Under the hood this is essentially how TS does overloads.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
1 approval PR that a maintainer has LGTM'd - any maintainer can merge this when ready enhancement: new plugin rule New rule request for eslint-plugin
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Add a new rule to enforce a method syntax
3 participants