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

Respect tslint configs hierarchical order #214

Merged
merged 16 commits into from
Feb 23, 2019
Merged

Respect tslint configs hierarchical order #214

merged 16 commits into from
Feb 23, 2019

Conversation

konstantinov90
Copy link
Contributor

Hi!

The "why" for this PR is in the README.md, please consider merging this request!

@johnnyreilly
Copy link
Member

johnnyreilly commented Feb 16, 2019

Could you elaborate please?

Sorry - I see what you mean! I've left some comments

@@ -90,7 +117,7 @@ export class ApiIncrementalChecker implements IncrementalCheckerInterface {
}

public hasLinter(): boolean {
return !!this.linterConfig;
return true;
Copy link
Member

Choose a reason for hiding this comment

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

This looks incorrect; not everyone wants to use tslint. Could you amend this please?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

fixed

linter.lint(
updatedFile,
undefined!,
this.linterConfig || this.getLinterConfig(updatedFile)
Copy link
Member

Choose a reason for hiding this comment

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

Can you talk me through this line please? What is this.linterConfig in this context? What would cause it to be false-y / when would you expect to call this.getLinterConfig(updatedFile)

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 tried to be more explicit this time around, but still: this.linterConfig - is a TSLint config, which is defined by path, provided by user in webpack config. Conversely, when user provides true instead of config-file path, this.linterConfig should be undefined, instead, it will be calculated inside this.getLinterConfig

@konstantinov90
Copy link
Contributor Author

The "why" for this PR is in the README.md, please consider merging this request!

Could you elaborate please?

Suppose I have a project with three folders

tslint.json
src/
    folder1/
        ...files...
    folder2/
        ...files...
    folder3/
        ...files...
        tslint.json

content of ./tslint.json

{
"extends": [  "tslint-react"  ],
"rules": {
   "no-any": true
 }
}

content of src/folder3/tslint.json

{
   extends: ["../../tslint.json"],
  "rules": {
        "no-any": false
   }
}

So with such a configuration I say that I want to use any inside src/folder3. And running tslint -p . shows that tslint indeed respects this overridden rule "no-any". Same goes to my IDE, it does respect this rule. And the only piece of puzzle is this great plugin, which, unfortunately, ignores "no-any": false inside folder3, because it assumes that only one config file exists. And this is what I am trying to fix

@johnnyreilly
Copy link
Member

johnnyreilly commented Feb 16, 2019

Cool - could you take a look at my review comments please?

@konstantinov90
Copy link
Contributor Author

Cool - could you take a look at my review comments please?

yeah, working on it)

@konstantinov90
Copy link
Contributor Author

/fixed

@johnnyreilly
Copy link
Member

Great - thanks! Could you write a test please? Integration makes the most sense I think

@konstantinov90
Copy link
Contributor Author

Great - thanks! Could you write a test please? Integration makes the most sense I think

will do

@konstantinov90
Copy link
Contributor Author

/test done

@johnnyreilly
Copy link
Member

Could you catch up your fork with the changes in master please? We've just pushed out a new version which affects this fork

@konstantinov90
Copy link
Contributor Author

Could you catch up your fork with the changes in master please? We've just pushed out a new version which affects this fork

done

}
this.linterConfigs[dirname] = config;
} else {
if (dirname === '.') {
Copy link
Member

Choose a reason for hiding this comment

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

can you talk me through what happens in this branch please? I've read it a bunch of times but I'm not so clear how this copes with:

  1. if there's only a tslint.json file in a particular directory of the application but not in the root
  2. if this genuinely manages to walk up the directory tree. When this.linterConfigs[dirname] = this.getLinterConfig(dirname); is invoked doesn't it stick with the current directory rather than walk up to the parent? Maybe I'm misunderstanding this...

Copy link
Contributor Author

Choose a reason for hiding this comment

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

  1. Do you mean "What if I want to subject to tslint some particular parts of my project by providing tslint.config inside, for example, src/components folder, but not in root folder"? I assumed that it is not a legitimate case, because of the chains of imports inside src/components that could lead to files from the root of project, that is why I throw an error, correct me if I'm wrong
  2. Follow these steps:
  • first file to be processed is src/components/SomeComponent.tsx. On the first run of getLinterConfig we get const dirname = 'src/components'
  • check if src/components/tslint.json exists (suppose it does not)
  • invoke second run of getLinterConfig('src/components')
  • const dirname = path.dirname('src/components') = 'src'
  • check src/tslint.json, if it does, then OK, we've got our config, if not - then invoke third run of getLinterConfig('src')
  • check ./tslint.json, if it does not exist - then throw an error

Copy link
Member

Choose a reason for hiding this comment

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

  1. Cool - that's clear thanks

I assumed that it is not a legitimate case, because of the chains of imports inside src/components that could lead to files from the root of project, that is why I throw an error, correct me if I'm wrong

I'm not sure it is an error... How does tslint itself behave in those circumstances? My assumption is that in this case we should just not lint...

Copy link
Contributor Author

Choose a reason for hiding this comment

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

yeah, you're right, TSLint CLI warns No valid rules have been specified for TypeScript files for root file, but still check inner files. Fixin' it

@@ -115,10 +145,6 @@ export class ApiIncrementalChecker implements IncrementalCheckerInterface {
}

public getLints(_cancellationToken: CancellationToken) {
if (!this.linterConfig) {
Copy link
Member

Choose a reason for hiding this comment

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

Does the removal of this speedy return mean that users who are not using tslint pay a performance tax as we still check for tslint.jsons?

If so I'd like to try and avoid this

Copy link
Contributor Author

@konstantinov90 konstantinov90 Feb 22, 2019

Choose a reason for hiding this comment

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

as far as I can see, the answer is no, because getLints method get called only if hasLinter returns true (check out run function in src/service.ts)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

but still, I guess it never hurts to be more explicit. Fixed it

Copy link
Member

Choose a reason for hiding this comment

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

So it looks like you're right - it does appear to be unnecessary. You had it right first time. 😁

Could you undo that - there's no point checking something a second time when it's already been checked.

I think then we'll be good to merge. Thanks for all your work!

Copy link
Contributor Author

Choose a reason for hiding this comment

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

you got it!

Copy link
Member

@johnnyreilly johnnyreilly left a comment

Choose a reason for hiding this comment

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

Actually one last thing: it looks like getLinterConfig is repeated in src/IncrementalChecker.ts and src/ApiIncrementalChecker.ts.

Is there a way we could convert this into a shared function both modules could use please? You'd probably need to supply config as a parameter for instance.

@konstantinov90
Copy link
Contributor Author

@johnnyreilly how about that?

@johnnyreilly
Copy link
Member

So I have bad news @konstantinov90. I'm a man with prejudices. Or let's call them "opinions" 😉

I don't find inheritance the most joyful thing to work with in a codebase. Whilst I completely acknowledge it could be used to solve this problem I'm afraid my innate dislike of inheritance means it troubles me deeply.

If you could indulge my preferences and go for an approach that doesn't involve inheritance I would greatly appreciate it 😏

@konstantinov90
Copy link
Contributor Author

konstantinov90 commented Feb 23, 2019

well then, I see two other options

  • composition, instead of inheritance, but it will possibly require not-so-minor refactoring (I mean create some helper class LinterConfigManager, which would incorporate singleFixedConfig or linterConfigs for the whole project filetree, linterExclusions and provide a method getConfigForFile(filepath), which would be called from within getLints method)
  • brutally extract function from both IncrementalChecker classes, but such a function would not be pure function, because (as it is now) it modifies this.linterConfigs and this.linterExclusions. Theoretically, this function could be more pure, if it would provide just configPath, but this would mean that we simply split the complexity of this function in two parts, and one part would still be the method of both IncrementalChecker classes.

What do you prefer?

@johnnyreilly
Copy link
Member

brutally extract function from both IncrementalChecker classes, but such a function would not be pure function, because (as it is now) it modifies this.linterConfigs and this.linterExclusions. Theoretically, this function could be more pure, if it would provide just configPath, but this would mean that we simply split the complexity of this function in two parts, and one part would still be the method of both IncrementalChecker classes.

I prefer this but I don't think it need be brutal. Create a shared makeGetLinterConfig function with parameters of linterConfigs / linterExclusions / whatever else is required. This function returns a getLinterConfig function and can operate on the supplied parameters to makeGetLinterConfig in closure scope.

Sound good?

@konstantinov90
Copy link
Contributor Author

/done

@johnnyreilly johnnyreilly merged commit 03c612e into TypeStrong:master Feb 23, 2019
@johnnyreilly
Copy link
Member

@konstantinov90 konstantinov90 deleted the respect-tslint-configs-hierarchical-order branch February 23, 2019 14:42
@konstantinov90
Copy link
Contributor Author

@johnnyreilly
Copy link
Member

Thanks for your work!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants