Added possibility to enforce different file name casings #4206
Added possibility to enforce different file name casings #4206
Conversation
Thanks for your interest in palantir/tslint, @cheeZery! Before we can accept your pull request, you need to sign our contributor license agreement - just visit https://cla.palantir.com/ and follow the instructions. Once you sign, I'll automatically update this pull request. |
src/rules/fileNameCasingRule.ts
Outdated
private static findApplicableCasing( | ||
fileBaseName: string, | ||
fileNameCasings: FileNameCasings | ||
): Casing | null { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| null
Why so many null
s instead of undefined
? It's generally preferable in JavaScript/TypeScript to prefer using undefined
, as it's the default value. TSLint typically goes by that preference.
Unless you have a real need for using null
, please switch to undefined
as defaults.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My bad, in my team I'm still fighting for strictNullChecks
, so as of now we don't really explicitly work with either undefined
or null
. But we're getting there 😊 While changing the code to use undefined
I realized that's way more intuitive, thanks for the suggestion!
src/rules/fileNameCasingRule.ts
Outdated
[ | ||
[".style.ts$", Casing.KebabCase], | ||
[".tsx$", Casing.PascalCase], | ||
[".*", Casing.CamelCase] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's generally convention to use objects for key/value pairs. Unless you have a specific need, please switch to something like:
{
".style.ts$": Casing.KebabCase,
".tsx$": Casing.PascalCase,
".*": Casing.CamelCase,
}
One benefit, in addition to being semantically more clear, is that you get rid of duplicate keys easily.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Will switch to object, thanks!
I'm embarassed to admit how long it took me to realize that the syntax in options
is actually JSON schema (since I never really worked with that). After that I optimized the declaration a bit, looking forward to your opinion after I pushed my changes.
Maybe the comment for options
in IRuleMetadata
should begin with "JSON schema ..." for people like me 😄
src/rules/fileNameCasingRule.ts
Outdated
}; | ||
/* tslint:enable:object-literal-sort-keys */ | ||
|
||
private static FAILURE_STRING(expectedCasing: Casing): string { | ||
return `File name must be ${Rule.stylizedNameForCasing(expectedCasing)}`; | ||
} | ||
|
||
private static isValidCasingOption(casing: string) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: instead of this dedicated function, you can keep a const validCasingOptions = new Set([Casing.CamelCase, ...]);
outside of the class and use its .has
. A little less code.
src/rules/fileNameCasingRule.ts
Outdated
const applicableCasing = fileNameCasings.find(fileNameCasing => { | ||
const fileNameMatch = fileNameCasing[0]; | ||
return ( | ||
Rule.isValidRegExp(fileNameMatch) && RegExp(fileNameMatch, "i").test(fileBaseName) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
...so if an invalid regular expression is provided, the rule will fail silently?
We should prefer to fail verbosely for invalid configurations. Either remove the isValidRegExp
check (+1) or console.warn
if it fails.
There are a lot of ways to provide invalid configurations, and it's near-impossible to guard against all of them. TSLint is moving towards standardized config options but it'll be a while before they're ready.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, there already were cases like this in the current implementation, see here for example: if the mandatory casing wasn't provided simply return an empty array 🤷♂️
I would like to fail more verbosely as you suggested, hence my question that you already answered below.
Currently there don't seem to be any console.warn
's in the rules, so maybe there's a better way? In objectLiteralSortKeys for example an actual Error
is thrown.
But I will go with what you guys suggest 🌝
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@JoshuaKGoldberg I just realized that it might be not a good idea to either throw an error or use console.warn, since this would happen for every linted file in the project. Using showWarningOnce
on the other should work, some rules even use this already. Any other suggestions?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agreed. Filed #4280 to tackle this more broadly.
In the meantime, feel free to either use the showWarningOnce
approach or not validate. IMO it shouldn't be each rule's responsibility to validate options ideally, since they already specify their format in their metadata... but since there isn't yet a method for that, it seems up to you.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the issue! (There's a small typo in my mention 😌) I now added some uses of showWarningOnce
A Great first contribution, by the way! 😊 |
Use key-value pair instead of tuple; several performance improvements; fix options JSON schema; show warnings in case of unexpected options
.vscode/launch.json
Outdated
@@ -5,15 +5,13 @@ | |||
"name": "Debug CLI", | |||
"type": "node", | |||
"request": "launch", | |||
"program": "${workspaceRoot}/build/src/tslint-cli.js", | |||
"program": "${workspaceRoot}/build/src/tslintCli.js", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ooh, mind taking these changes out into a separate PR? Definitely a bug in launch.json
that the file hasn't been renamed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Don't mind at all, it's better to separate those things. Reverted my changes and will open a new PR for that fix.
src/rules/fileNameCasingRule.ts
Outdated
** \`${Casing.KebabCase}\`: File names must be kebab-cased: \`file-name.ts\`. | ||
** \`${Casing.SnakeCase}\`: File names must be snake-cased: \`file_name.ts\`. | ||
|
||
* The array again consists of array with two items. The first item must be a case-insenstive |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The array again
Should this be updated to mention that it takes in an object?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yep, forgot that, thanks. I changed it now but since English isn't my first language feel free to point out anything that could be phrased better!
Will be done in separate PR
@JoshuaKGoldberg, I also now saw the duplicate PR #4280. Both PR pretty much do the same, in #4280 there are a more functions "outsourced" and a bit more types, here you have some additional checks and console warnings. I'm not sure which one should be merged 🤷♂️ |
I love this, and I would propose to merge that part of the PR into my version (with your commits sign off @cheeZery I wouldn't like it otherwise to be honest) also the extra testings. Also the other PR do not have extra changes unrelated to the issue so should be simple to review. The functions could be move into the class definition if you want, but I would say I prefer to have the concept of the different validators as I did. Specially that reading each body of the functions you could map your head around it easier.
Technically we are doing also the same but I decided to move different sections into functions separating the responsabilities. |
@JoshuaKGoldberg sorry I bother you with this but should we do about this? Whichever is the case I would love to have this config published so I can start using it on my packages but we need your direction and support on this. |
@yordis I too like the idea to get the best of both PRs. I guess I even like your approach, to define the functions outside of the class scope better! |
Great, let's do that! Thanks you two 😍 In summary, I'll:
|
Perfect! |
@cheeZery does this merge look good to you? |
LOVE IT ❤️ 💟 Best of both PRs 💟 ❤️ 👏 |
@JoshuaKGoldberg it does indeed look good to me 😊 I agree with @yordis 👬 Thanks to the both of you! |
Awesome, thanks everyone! |
PR checklist
Overview of change:
Enhanced file-name-casing rule by adding possibility to enforce casing per file name match (case-insentive RegExp).
Is there anything you'd like reviewers to focus on?
Since this is my first contribution to this repo, I'd like you to be very thoroughly in your review :-) I also wasn't familiar with the syntax for the
options
object, so that might be improvable.And I only now see, that there's another PR (#4204, @rwaskiewicz) which also enhances this very rule by adding an exclusion option. Maybe that feature and this one can be combined by adding an additional "ignore" option to the file matcher argument. For example:
By the way, I simply defined the RegExp as being case-insensitive but this can of course easily be changed. The check will currently also only consider the file name and its extension. I thought about taking the whole path in consideration but I guess that would have been more complicated due to two reasons: the different file separators (
/
vs.\
), and that we probably should use the relative path from the project base dir or the tslint.json fileIn addition to that please consider the fact, that there are now some cases were the rule could produce false-positives for invalid rule arguments.
For example when the rule's arguments are
[true, "camelCase"]
(instead of "camel-case") the linter will now ignore this option. Prior to my change this was somewhat of a false-negative, because TSLint printed the error "File name must be undefined" (for every file).And when the user now enters an invalid RegExp as a file match, that entry will be ignored.
Can and should the user be informed about those things with some kind of warning?
(Last but not least I stumbled upon a type in the
launch.json
and fixed it, hope that was fine)CHANGELOG.md entry:
[new-rule-option] File name whitelist for
file-name-casing