Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 48 additions & 4 deletions src/configuration.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { readSync, readFileSync } from 'fs';
import path from 'path';
import { sync as globSync, hasMagic as globHasMagic } from 'glob';

import ModuleResolver from './relative_module_resolver.js';
import { SourceMap } from './source_map.js';
import JSONFormatter from './formatters/json_formatter.js';
import TextFormatter from './formatters/text_formatter.js';
Expand All @@ -15,6 +16,7 @@ export class Configuration {
- format: (required) `text` | `json`
- rules: [string array] whitelist rules
- schemaPaths: [string array] file(s) to read schema from
- customRulePackages: [string array] names of packages where the entry point named exports are rules
- customRulePaths: [string array] path to additional custom rules to be loaded
- stdin: [boolean] pass schema via stdin?
- commentDescriptions: [boolean] use old way of defining descriptions in GraphQL SDL
Expand All @@ -23,6 +25,7 @@ export class Configuration {
constructor(options = {}, stdinFd = null) {
const defaultOptions = {
format: 'text',
customRulePackages: [],
customRulePaths: [],
commentDescriptions: false,
oldImplementsSyntax: false,
Expand All @@ -36,6 +39,7 @@ export class Configuration {
this.schema = null;
this.sourceMap = null;
this.rules = null;
this.rulePackages = this.options.customRulePackages;
this.builtInRulePaths = path.join(__dirname, 'rules/*.js');
this.rulePaths = this.options.customRulePaths.concat(this.builtInRulePaths);
}
Expand Down Expand Up @@ -129,11 +133,30 @@ export class Configuration {
return this.rules;
}

this.rules = this.getRulesFromPaths(this.rulePaths);
const packageRules = this.getRulesFromPackages(this.rulePackages);
const pathRules = this.getRulesFromPaths(this.rulePaths);

this.rules = packageRules.concat(pathRules);

return this.rules;
}

getRulesFromPackages(rulePackages) {
const rules = new Set([]);

rulePackages.map(rulePackage => {
// We can't simply call `require()` because it needs to be from the project's node_modules
const rulePackagePath = ModuleResolver.resolve(
rulePackage,
path.join(process.cwd(), '__placeholder__.js')
);
Comment on lines +148 to +152
Copy link
Owner

Choose a reason for hiding this comment

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

Ohhhh, that's interesting. I hadn't even considered that. 🤔

Btw did the path you are passing need to be a file and not a folder? In other words, the __placeholder__.js was required and is okay if it does not actually exist?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@cjoudrey I actually borrowed this bit of code from the eslint packages: https://github.com/eslint/eslint/blob/a47770706ac59633dcd73e886d1a7282b324ee06/lib/init/config-initializer.js#L350

I actually don't know if a folder would work, but they probably added the placeholder for a reason? 😄If I remember when I'm finishing this PR, I'll see if it works with just the CWD.

let ruleMap = require(rulePackagePath);
Object.keys(ruleMap).forEach(k => rules.add(ruleMap[k]));
});

return Array.from(rules);
}

getRulesFromPaths(rulePaths) {
const expandedPaths = expandPaths(rulePaths);
const rules = new Set([]);
Expand All @@ -156,23 +179,43 @@ export class Configuration {
let rules;

try {
rules = this.getAllRules();
rules = this.getRulesFromPackages(this.rulePackages);
} catch (e) {
if (e.code === 'MODULE_NOT_FOUND') {
issues.push({
message: `There was an issue loading the specified custom rules: '${
e.message.split('\n')[0]
}'`,
field: 'custom-rule-paths',
field: 'custom-rule-packages',
type: 'error',
});

rules = this.getAllBuiltInRules();
rules = [];
} else {
throw e;
}
}

try {
rules = rules.concat(this.getRulesFromPaths(this.rulePaths));
} catch (e) {
if (e.code === 'MODULE_NOT_FOUND') {
issues.push({
message: `There was an issue loading the specified custom rules: '${
e.message.split('\n')[0]
}'`,
field: 'custom-rule-paths',
type: 'error',
});
} else {
throw e;
}
}

if (rules.length === 0) {
rules = this.getAllBuiltInRules();
}

const ruleNames = rules.map(rule => rule.name);

let misConfiguredRuleNames = []
Expand Down Expand Up @@ -234,6 +277,7 @@ function loadOptionsFromConfig(configDirectory) {

return {
rules: cosmic.config.rules,
customRulePackages: cosmic.config.customRulePackages,
customRulePaths: customRulePaths || [],
schemaPaths: schemaPaths,
};
Expand Down
42 changes: 42 additions & 0 deletions src/relative_module_resolver.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
/**
* Utility for resolving a module relative to another module
* @author Teddy Katz
* Copied from https://github.com/eslint/eslint/blob/master/lib/shared/relative-module-resolver.js
*/

'use strict';

const Module = require('module');

/*
* `Module.createRequire` is added in v12.2.0. It supports URL as well.
* We only support the case where the argument is a filepath, not a URL.
*/
const createRequire = Module.createRequire || Module.createRequireFromPath;

module.exports = {
/**
* Resolves a Node module relative to another module
* @param {string} moduleName The name of a Node module, or a path to a Node module.
* @param {string} relativeToPath An absolute path indicating the module that `moduleName` should be resolved relative to. This must be
* a file rather than a directory, but the file need not actually exist.
* @returns {string} The absolute path that would result from calling `require.resolve(moduleName)` in a file located at `relativeToPath`
*/
resolve(moduleName, relativeToPath) {
try {
return createRequire(relativeToPath).resolve(moduleName);
} catch (error) {
// This `if` block is for older Node.js than 12.0.0. We can remove this block in the future.
if (
typeof error === 'object' &&
error !== null &&
error.code === 'MODULE_NOT_FOUND' &&
!error.requireStack &&
error.message.includes(moduleName)
) {
error.message += `\nRequire stack:\n- ${relativeToPath}`;
}
throw error;
}
},
};