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

resolve rulesDirectory - fixes #2163 #2358

Merged

Conversation

donaldpipowitch
Copy link
Contributor

@donaldpipowitch donaldpipowitch commented Mar 16, 2017

PR checklist

Overview of change:

When someone uses a rulesDirectory we try to resolve it with Node resolve logic first, before falling back to the old algorithm.

CHANGELOG.md entry:

~[enhancement] rulesDirectory can now be resolved with dir-resolve (#2163)~~
[enhancement] rulesDirectory can now be resolved with Nodes resolve logic, if the directory contains an index.js (#2163)

@ajafff
Copy link
Contributor

ajafff commented Mar 16, 2017

Would be great if this could work with the main field in package.json. Then you would only need to specify the package name in rulesDirectory and the package.json points to the right subdirectory.

This may conflict with the module resolution that is done for finding configs, though.

@donaldpipowitch
Copy link
Contributor Author

I'm not sure if this would be a good addition. AFAIK main should point to a file, not a directory.

@donaldpipowitch donaldpipowitch changed the title fixes #2163 resolve rulesDirectory - fixes #2163 Mar 17, 2017
@donaldpipowitch
Copy link
Contributor Author

Who could review this? @nchen63 or @ajafff? Anything I can do to help? :)

@ajafff
Copy link
Contributor

ajafff commented Mar 24, 2017

Sorry for the delayed response.
Changes LGTM, but my comment from above still stands.

I'm not sure if this would be a good addition. AFAIK main should point to a file, not a directory.

I get your point, but then there is this commit by @jkillian which suggests adding an (empty) index.js to resolve to the correct directory.
buzinas/tslint-eslint-rules@aa17d2e

That's how the configuration directory is resolved.
If rulesDirectory is resolved in the same way, rules and configs would need to be located in the same directory. But that's not too bad, I think.

As a bonus that wouldn't add another dependency, because we can simply use https://www.npmjs.com/package/resolve for both.

@ajafff
Copy link
Contributor

ajafff commented Mar 24, 2017

Additions to my last comment:

rules and configs would need to be located in the same directory

that's not entirely true. you can still use package/rules although the rules directory also needs an index.js

@donaldpipowitch you should also check the path first before resolving as package. When the path starts with /, ./ or ../ or equals . or .. you don't resolve but use it as a path.

And there need to be tests for this new feature. The best place to start is test/ruleLoaderTests.ts

Copy link
Contributor

@adidahiya adidahiya left a comment

Choose a reason for hiding this comment

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

I agree with @ajafff on most of those points ^

also, yeah, needs tests :)

@donaldpipowitch
Copy link
Contributor Author

Cool. Thank you both for the good feedback. I'll try to make the changes in the next days :)

@donaldpipowitch donaldpipowitch force-pushed the 2163-resolve-rules-directory branch 4 times, most recently from f4c8d0e to c2e1ad7 Compare April 1, 2017 20:38
@donaldpipowitch
Copy link
Contributor Author

I updated my PR.

The logic for resolving the directory is now inside configuration.ts:getRulesDirectories and not ruleLoader.ts:loadCachedRule. It looks like this one is the better place to do this...? getRulesDirectories is called before loadCachedRule.

I get your point, but then there is this commit by @jkillian which suggests adding an (empty) index.js to resolve to the correct directory.
As a bonus that wouldn't add another dependency, because we can simply use https://www.npmjs.com/package/resolve for both.
@donaldpipowitch you should also check the path first before resolving as package. When the path starts with /, ./ or ../ or equals . or .. you don't resolve but use it as a path.

I did all that. I tested this with the "rulesDirectory": [ "tslint-eslint-rules/dist/rules" ], and manually adding an empty index.js into "tslint-eslint-rules/dist/rules". The rule was loaded correctly, but I got a warning when a linter rule failed (Warning: this.createFix is not a function - but it looks like this is a young breaking change #2403 and not fixed by tslint-eslint-rule yet).

And there need to be tests for this new feature.

Can you give me more guidance to test this? Do I need to mock a 3rd party node module to do that correctly?

@ajafff
Copy link
Contributor

ajafff commented Apr 1, 2017

Do I need to mock a 3rd party node module to do that correctly?

There is already. configurationTests.ts uses two packages for testing, see https://github.com/palantir/tslint/blob/master/test/config/package.json
You can probably use it for your test, too.

@@ -277,6 +277,12 @@ export function getRelativePath(directory?: string | null, relativeTo?: string)
return undefined;
}

// check if directory should be used as path or if it should be resolved like a module
export function useAsPath(directory: string) {
return directory.startsWith("/") || directory.startsWith("./") || directory.startsWith("../")
Copy link
Contributor

Choose a reason for hiding this comment

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

this should probably include paths starting with node_modules/

to simplify the check, you can use this regex:

/^(?:\.?\.?(?:\/|$)|node_modules\/)/.test(directory)

try {
absolutePath = resolve.sync(dir, { basedir: relativeTo }).replace("index.js", "");
} catch (err) {
throw new Error(`Could not find custom rule directory: ${dir}`);
Copy link
Contributor

Choose a reason for hiding this comment

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

instead of throwing here, it should fall back using path as directory

}
} else {
try {
absolutePath = resolve.sync(dir, { basedir: relativeTo }).replace("index.js", "");
Copy link
Contributor

Choose a reason for hiding this comment

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

just to be safe, because the path may include index.js earlier: .replace(/index.js$/, "")

return arrayify(directories)
.map((dir) => {
let absolutePath;
if (useAsPath(dir)) {
Copy link
Contributor

Choose a reason for hiding this comment

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

to avoid code duplication, consider writing it as follows:

if (!useAsPath(dir)) {
    try {
        return resolve.sync(...).replace(...);
    } catch (err) {
        // swallow error and fallback to using directory as path
    }
}
const absolutePath = getRelativePath(...);
if (absolutePath != null) {
    ...
}
return absolutePath;

Copy link
Contributor Author

Choose a reason for hiding this comment

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

👍

@donaldpipowitch
Copy link
Contributor Author

Is this fine now?

Copy link
Contributor

@ajafff ajafff left a comment

Choose a reason for hiding this comment

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

Almost done, I had only a few things to comment.

In addition this feature needs a bit of documentation.
https://github.com/palantir/tslint/blob/master/docs/usage/configuration/index.md should contain some information that rulesDirectory tries to resolve to a package in node_modules.
It should also suggest prefixing relative paths with ./ to avoid module resolution.

@@ -0,0 +1,3 @@
{
"rulesDirectory": ["tslint-test-custom-rules/rules"]
Copy link
Contributor

Choose a reason for hiding this comment

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

we should also have tests to ensure that the fallback works. for that you can simply add an entry like "test/external/tslint-test-custom-rules/rules"

Copy link
Contributor

Choose a reason for hiding this comment

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

btw. the package tslint-test-custom-rules needs a index.js in its rules directory

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 add a test, but didn't use "test/external/tslint-test-custom-rules/rules", because it would throw. I'm not sure, this works now as you expected...?

.map((dir) => {
if (!useAsPath(dir)) {
try {
return resolve.sync(dir, { basedir: relativeTo }).replace(/index.js$/, "");
Copy link
Contributor

Choose a reason for hiding this comment

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

if main in package.json does not point to a file called index.js, this will not quite work. maybe we should use path.dirname(...) instead of .replace(...)

Copy link
Contributor

@ajafff ajafff left a comment

Choose a reason for hiding this comment

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

Bear with me, I hope that's the last iteration

@@ -0,0 +1,3 @@
{
"rulesDirectory": ["../external/tslint-test-custom-rules/rules"]
Copy link
Contributor

Choose a reason for hiding this comment

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

Actually I wanted a test where the path was relative but did not start with ./ or ../
Consider adding a folder test/config/relativeRulesDirectory and change this line to "rulesDirectory": ["relativeRulesDirectory"]

@@ -252,6 +252,11 @@ describe("Configuration", () => {
assert.equal("off", config.rules.get("no-eval")!.ruleSeverity);
});

it("resolve rule directory from package", () => {
assert.doesNotThrow(() => loadConfigurationFromPath("./test/config/tslint-custom-rules-with-package.json"));
assert.doesNotThrow(() => loadConfigurationFromPath("./test/config/tslint-custom-rules-with-package-fallback.json"));
Copy link
Contributor

Choose a reason for hiding this comment

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

consider checking if it resolved to the expected rulesDirectory in both cases

@donaldpipowitch
Copy link
Contributor Author

@ajafff Thank you for reviewing this :) I updated the tests. I think it looks good now.

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

Successfully merging this pull request may close these issues.

None yet

3 participants