Skip to content

Commit

Permalink
New: Support configuration rules in global
Browse files Browse the repository at this point in the history
  • Loading branch information
sarvaje authored and alrra committed Mar 1, 2018
1 parent 095d297 commit 5cde8da
Show file tree
Hide file tree
Showing 5 changed files with 78 additions and 23 deletions.
4 changes: 3 additions & 1 deletion packages/sonarwhal/src/lib/config.ts
Expand Up @@ -192,7 +192,7 @@ const validateRules = (rulesConfig: RulesConfigObject, userConfig: UserConfig) =
const rules = Object.keys(rulesConfig);

rules.forEach((rule) => {
const Rule = resourceLoader.loadRule(rule);
const Rule = resourceLoader.loadRule(rule, userConfig.extends);

const valid: boolean = validateRule(Rule.meta, userConfig.rules[rule], rule);

Expand All @@ -210,13 +210,15 @@ export class SonarwhalConfig {
public readonly parsers: Array<string>;
public readonly rules: RulesConfigObject;
public readonly rulesTimeout: number;
public readonly extends: Array<string>;

private constructor(userConfig: UserConfig, browsers: Array<string>, ignoredUrls, rules: RulesConfigObject) {
this.browserslist = browsers;
this.formatters = userConfig.formatters;
this.ignoredUrls = ignoredUrls;
this.parsers = userConfig.parsers;
this.rules = rules;
this.extends = userConfig.extends;

this.rulesTimeout = userConfig.rulesTimeout || 60000;

Expand Down
80 changes: 61 additions & 19 deletions packages/sonarwhal/src/lib/utils/resource-loader.ts
Expand Up @@ -139,6 +139,35 @@ const getResource = (source: string, type: ResourceType, name: string) => {
return null;
};

/**
* Looks inside the configurations looking for resources.
*/
const generateConfigPathsToResources = (configurations: Array<string>, name: string, type: ResourceType) => {
return configurations.reduce((total: Array<string>, configuration: string) => {
const basePackagePaths = ['@sonarwhal/configuration-', 'sonarwhal-configuration-'];

let result = total;

for (const basePackagePath of basePackagePaths) {
const packageName = `${basePackagePath}${configuration}`;

try {
const packagePath = path.dirname(require.resolve(packageName));

const resourcePackages = globby.sync(`node_modules/{@sonarwhal/,sonarwhal-}${type}-${name}/package.json`, { absolute: true, cwd: packagePath }).map((pkg) => {
return path.dirname(pkg);
});

result = result.concat(resourcePackages);
} catch (err) {
debug(`Package ${packageName} not found`);
}
}

return result;
}, []);
};

/**
* Looks for a sonarwhal resource with the given `name` and tries to load it.
* If no valid resource is found, it throws an `Error`.
Expand All @@ -151,7 +180,7 @@ const getResource = (source: string, type: ResourceType, name: string) => {
* 4. external rules
*
*/
export const loadResource = (name: string, type: ResourceType, verifyVersion = false) => {
export const loadResource = (name: string, type: ResourceType, configurations: Array<string> = [], verifyVersion = false) => {
debug(`Searching ${name}…`);

const packageName = name.includes('/') ? name.split('/')[0] : name;
Expand All @@ -164,42 +193,55 @@ export const loadResource = (name: string, type: ResourceType, verifyVersion = f
return resources.get(key);
}

const configPathsToResources = generateConfigPathsToResources(configurations, packageName, type);

const sources: Array<string> = [
`@sonarwhal/${key}`, // Officially supported package
`sonarwhal-${key}`, // Third party package
path.normalize(`${SONARWHAL_ROOT}/dist/src/lib/${type}s/${packageName}/${packageName}.js`) // Part of core. E.g.: built-in formatters, parsers, connectors
// path.normalize(`${path.resolve(SONARWHAL_ROOT, '..')}/${key}`) // Things under `/packages/` for when we are developing something official. E.g.: `/packages/rule-http-cache`
];
].concat(configPathsToResources);

let resource;
let resourcePath: string;
let isValid: boolean = true;

sources.some((source: string) => {
resource = getResource(source, type, resourceName);
if (resource) {
const res = getResource(source, type, resourceName);

if (res) {
debug(`${name} found in ${source}`);
resourcePath = source;

if (verifyVersion && !isVersionValid(source)) {
debug(`Resource ${name} isn't compatible with current sonarwhal version`);

isValid = false;

return false;
}

isValid = true;
resource = res;
}

return resource;
});

if (!isValid) {
throw new Error(`Resource ${name} isn't compatible with current sonarwhal version`);
}

if (!resource) {
debug(`Resource ${name} not found`);
throw new Error(`Resource ${name} not found`);
}

if (verifyVersion && !isVersionValid(resourcePath)) {
debug(`Resource ${name} isn't compatible with current sonarwhal version`);
throw new Error(`Resource ${name} isn't compatible with current sonarwhal version`);
}

resources.set(key, resource);

return resource;
};

const loadListOfResources = (list: Array<string> | Object, type: ResourceType): { incompatible: Array<string>, missing: Array<string>, resources: Array<any> } => {

const loadListOfResources = (list: Array<string> | Object, type: ResourceType, configurations: Array<string> = []): { incompatible: Array<string>, missing: Array<string>, resources: Array<any> } => {
const missing: Array<string> = [];
const incompatible: Array<string> = [];

Expand All @@ -210,7 +252,7 @@ const loadListOfResources = (list: Array<string> | Object, type: ResourceType):

const loadedResources = items.reduce((loaded, resourceId) => {
try {
const resource = loadResource(resourceId, type, true);
const resource = loadResource(resourceId, type, configurations, true);

loaded.push(resource);
} catch (e) {
Expand All @@ -231,8 +273,8 @@ const loadListOfResources = (list: Array<string> | Object, type: ResourceType):
};
};

export const loadRule = (ruleId: string): IRuleConstructor => {
return loadResource(ruleId, ResourceType.rule);
export const loadRule = (ruleId: string, configurations: Array<string>): IRuleConstructor => {
return loadResource(ruleId, ResourceType.rule, configurations);
};

export const loadConfiguration = (configurationId: string) => {
Expand All @@ -245,14 +287,14 @@ export const loadResources = (config: SonarwhalConfig): SonarwhalResources => {
let connector = null;

try {
connector = loadResource(config.connector.name, ResourceType.connector, true);
connector = loadResource(config.connector.name, ResourceType.connector, config.extends, true);
} catch (e) {
console.error(e);
}

const { incompatible: incompatibleRules, resources: rules, missing: missingRules } = loadListOfResources(config.rules, ResourceType.rule);
const { incompatible: incompatibleParsers, resources: parsers, missing: missingParsers } = loadListOfResources(config.parsers, ResourceType.parser);
const { incompatible: incompatibleFormatters, resources: formatters, missing: missingFormatters } = loadListOfResources(config.formatters, ResourceType.formatter);
const { incompatible: incompatibleRules, resources: rules, missing: missingRules } = loadListOfResources(config.rules, ResourceType.rule, config.extends);
const { incompatible: incompatibleParsers, resources: parsers, missing: missingParsers } = loadListOfResources(config.parsers, ResourceType.parser, config.extends);
const { incompatible: incompatibleFormatters, resources: formatters, missing: missingFormatters } = loadListOfResources(config.formatters, ResourceType.formatter, config.extends);
const missing = [].concat(missingRules, missingParsers, missingFormatters);
const incompatible = [].concat(incompatibleFormatters, incompatibleParsers, incompatibleRules);

Expand Down
2 changes: 1 addition & 1 deletion packages/sonarwhal/tests/helpers/rule-runner.ts
Expand Up @@ -172,7 +172,7 @@ export const testRule = (ruleId: string, ruleTests: Array<RuleTest>, configs: {
});
};

const Rule: IRuleConstructor = resourceLoader.loadRule(ruleId);
const Rule: IRuleConstructor = resourceLoader.loadRule(ruleId, []);

/* Run all the tests for a given rule in all connectors. */
connectors.forEach((connector) => {
Expand Down
10 changes: 10 additions & 0 deletions packages/sonarwhal/tests/lib/sonarwhal.ts
Expand Up @@ -106,6 +106,7 @@ test.serial(`If config.rules has some rules "off", we shouldn't create those rul
new Sonarwhal({
browserslist: null,
connector: { name: 'connector' },
extends: [],
formatters: [],
ignoredUrls: [],
parsers: [],
Expand Down Expand Up @@ -167,6 +168,7 @@ test.serial(`If a rule has the metadata "ignoredConnectors" set up, we shouldn't
new Sonarwhal({
browserslist: null,
connector: { name: 'jsdom' },
extends: [],
formatters: [],
ignoredUrls: [],
parsers: [],
Expand Down Expand Up @@ -232,6 +234,7 @@ test.serial(`If a rule has the metadata "ignoredConnectors" set up, we should ig
new Sonarwhal({
browserslist: null,
connector: { name: 'chrome' },
extends: [],
formatters: [],
ignoredUrls: [],
parsers: [],
Expand Down Expand Up @@ -289,6 +292,7 @@ test.serial(`If the rule scope is 'local' and the connector isn't local the rule
new Sonarwhal({
browserslist: null,
connector: { name: 'chrome' },
extends: [],
formatters: [],
ignoredUrls: [],
parsers: [],
Expand Down Expand Up @@ -346,6 +350,7 @@ test.serial(`If the rule scope is 'site' and the connector is local the rule sho
new Sonarwhal({
browserslist: null,
connector: { name: 'local' },
extends: [],
formatters: [],
ignoredUrls: [],
parsers: [],
Expand Down Expand Up @@ -403,6 +408,7 @@ test.serial(`If the rule scope is 'any' and the connector is local the rule shou
new Sonarwhal({
browserslist: null,
connector: { name: 'local' },
extends: [],
formatters: [],
ignoredUrls: [],
parsers: [],
Expand Down Expand Up @@ -462,6 +468,7 @@ test.serial(`If the rule scope is 'any' and the connector isn't local the rule s
new Sonarwhal({
browserslist: null,
connector: { name: 'chrome' },
extends: [],
formatters: [],
ignoredUrls: [],
parsers: [],
Expand Down Expand Up @@ -489,6 +496,7 @@ test.serial(`If an event is emitted for an ignored url, it shouldn't propagate`,
const sonarwhalObject = new Sonarwhal({
browserslist: null,
connector: { name: 'connector' },
extends: [],
formatters: [],
ignoredUrls: new Map([['all', [/.*\.domain1\.com\/.*/i]]]),
parsers: [],
Expand Down Expand Up @@ -533,6 +541,7 @@ test.serial(`If a rule is ignoring some url, it shouldn't run the event`, (t) =>
new Sonarwhal({
browserslist: null,
connector: { name: 'connector' },
extends: [],
formatters: [],
ignoredUrls: new Map([['all', [/.*\.domain1\.com\/.*/i]], ['disallowed-headers', [/.*\.domain2\.com\/.*/i]]]),
parsers: [],
Expand Down Expand Up @@ -582,6 +591,7 @@ test.serial(`If a rule is taking too much time, it should be ignored after the c
new Sonarwhal({
browserslist: null,
connector: { name: 'connector' },
extends: [],
formatters: [],
ignoredUrls: new Map(),
parsers: [],
Expand Down
5 changes: 3 additions & 2 deletions packages/sonarwhal/tests/lib/utils/resource-loader.ts
Expand Up @@ -144,7 +144,7 @@ test('loadResource throws an error if the version is incompatible when using "ve
tryToLoadFromStub.returns(fakeResource);

const { message } = t.throws(() => {
resourceLoader.loadResource('another-fake-resource', 'formatter', true);
resourceLoader.loadResource('another-fake-resource', 'formatter', [], true);
});

t.is(message, `Resource another-fake-resource isn't compatible with current sonarwhal version`, 'Received a different exception');
Expand All @@ -169,7 +169,7 @@ test('loadResource returns the resource if versions are compatible', (t) => {

tryToLoadFromStub.returns(fakeResource);

const resource = resourceLoader.loadResource('another-fake-resource', 'formatter', true);
const resource = resourceLoader.loadResource('another-fake-resource', 'formatter', [], true);

t.is(resource, fakeResource, `Resources aren't the same`);
});
Expand All @@ -183,6 +183,7 @@ test('loadResources loads all the resources of a given config', (t) => {
name: 'jsdom',
options: {}
},
extends: [],
formatters: ['json'],
ignoredUrls: [],
parsers: [],
Expand Down

0 comments on commit 5cde8da

Please sign in to comment.