Skip to content
This repository has been archived by the owner on Mar 25, 2021. It is now read-only.

Added possibility to enforce different file name casings #4206

Merged

Conversation

cheeZery
Copy link
Contributor

@cheeZery cheeZery commented Oct 7, 2018

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:

"file-name-casing": [true, [
    "index\.ts": "ignore",
    "\.ts$": "camel-case",
]];

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 file

In 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

@palantirtech
Copy link
Member

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.

private static findApplicableCasing(
fileBaseName: string,
fileNameCasings: FileNameCasings
): Casing | null {
Copy link
Contributor

Choose a reason for hiding this comment

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

| null

Why so many nulls 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.

Copy link
Contributor Author

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!

[
[".style.ts$", Casing.KebabCase],
[".tsx$", Casing.PascalCase],
[".*", Casing.CamelCase]
Copy link
Contributor

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.

Copy link
Contributor Author

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 😄

};
/* 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) {
Copy link
Contributor

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.

const applicableCasing = fileNameCasings.find(fileNameCasing => {
const fileNameMatch = fileNameCasing[0];
return (
Rule.isValidRegExp(fileNameMatch) && RegExp(fileNameMatch, "i").test(fileBaseName)
Copy link
Contributor

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.

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, 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 🌝

Copy link
Contributor Author

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?

Copy link
Contributor

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.

Copy link
Contributor Author

@cheeZery cheeZery Nov 12, 2018

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

@JoshuaKGoldberg
Copy link
Contributor

JoshuaKGoldberg commented Nov 7, 2018

Can and should the user be informed about those things with some kind of warning?

A console.warn seems like a good step in this direction, though agreed that there's not much precedent here.

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
@@ -5,15 +5,13 @@
"name": "Debug CLI",
"type": "node",
"request": "launch",
"program": "${workspaceRoot}/build/src/tslint-cli.js",
"program": "${workspaceRoot}/build/src/tslintCli.js",
Copy link
Contributor

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.

Copy link
Contributor Author

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.

** \`${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
Copy link
Contributor

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?

Copy link
Contributor Author

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!

@cheeZery
Copy link
Contributor Author

cheeZery commented Nov 13, 2018

@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 🤷‍♂️
Maybe we can get the best of both by working together in on one PR :)

@yordis
Copy link
Contributor

yordis commented Nov 16, 2018

here you have some additional checks and console warnings.

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.

validate exists because it is a factory function basically responsable for deciding what validatio you should use.

Technically we are doing also the same but I decided to move different sections into functions separating the responsabilities.

@yordis
Copy link
Contributor

yordis commented Nov 21, 2018

@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.

@cheeZery
Copy link
Contributor Author

@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!
I wouldn't mind if your PR would be merged first and after that we take a look and figure out what of my PR is still needed and merge that afterwards :-)

@JoshuaKGoldberg
Copy link
Contributor

Great, let's do that! Thanks you two 😍

In summary, I'll:

@cheeZery
Copy link
Contributor Author

Perfect!

@JoshuaKGoldberg
Copy link
Contributor

@cheeZery does this merge look good to you?

@yordis
Copy link
Contributor

yordis commented Nov 22, 2018

LOVE IT ❤️ 💟 Best of both PRs 💟 ❤️ 👏

@cheeZery
Copy link
Contributor Author

cheeZery commented Nov 23, 2018

@JoshuaKGoldberg it does indeed look good to me 😊 I agree with @yordis 👬 Thanks to the both of you!

@JoshuaKGoldberg
Copy link
Contributor

Awesome, thanks everyone!

@JoshuaKGoldberg JoshuaKGoldberg merged commit a43cfdc into palantir:master Nov 23, 2018
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants