From 5cde8da7eb7d64079ab6c626d1dd48107730d8d0 Mon Sep 17 00:00:00 2001 From: Jesus David Garcia Gomez Date: Wed, 28 Feb 2018 16:16:47 -0800 Subject: [PATCH] New: Support configuration rules in global --- packages/sonarwhal/src/lib/config.ts | 4 +- .../src/lib/utils/resource-loader.ts | 80 ++++++++++++++----- .../sonarwhal/tests/helpers/rule-runner.ts | 2 +- packages/sonarwhal/tests/lib/sonarwhal.ts | 10 +++ .../tests/lib/utils/resource-loader.ts | 5 +- 5 files changed, 78 insertions(+), 23 deletions(-) diff --git a/packages/sonarwhal/src/lib/config.ts b/packages/sonarwhal/src/lib/config.ts index fdb22409ccc..88a4685f1c2 100644 --- a/packages/sonarwhal/src/lib/config.ts +++ b/packages/sonarwhal/src/lib/config.ts @@ -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); @@ -210,6 +210,7 @@ export class SonarwhalConfig { public readonly parsers: Array; public readonly rules: RulesConfigObject; public readonly rulesTimeout: number; + public readonly extends: Array; private constructor(userConfig: UserConfig, browsers: Array, ignoredUrls, rules: RulesConfigObject) { this.browserslist = browsers; @@ -217,6 +218,7 @@ export class SonarwhalConfig { this.ignoredUrls = ignoredUrls; this.parsers = userConfig.parsers; this.rules = rules; + this.extends = userConfig.extends; this.rulesTimeout = userConfig.rulesTimeout || 60000; diff --git a/packages/sonarwhal/src/lib/utils/resource-loader.ts b/packages/sonarwhal/src/lib/utils/resource-loader.ts index b9e914cfa50..1d9ae5d9121 100644 --- a/packages/sonarwhal/src/lib/utils/resource-loader.ts +++ b/packages/sonarwhal/src/lib/utils/resource-loader.ts @@ -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, name: string, type: ResourceType) => { + return configurations.reduce((total: Array, 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`. @@ -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 = [], verifyVersion = false) => { debug(`Searching ${name}…`); const packageName = name.includes('/') ? name.split('/')[0] : name; @@ -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 = [ `@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 | Object, type: ResourceType): { incompatible: Array, missing: Array, resources: Array } => { + +const loadListOfResources = (list: Array | Object, type: ResourceType, configurations: Array = []): { incompatible: Array, missing: Array, resources: Array } => { const missing: Array = []; const incompatible: Array = []; @@ -210,7 +252,7 @@ const loadListOfResources = (list: Array | 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) { @@ -231,8 +273,8 @@ const loadListOfResources = (list: Array | Object, type: ResourceType): }; }; -export const loadRule = (ruleId: string): IRuleConstructor => { - return loadResource(ruleId, ResourceType.rule); +export const loadRule = (ruleId: string, configurations: Array): IRuleConstructor => { + return loadResource(ruleId, ResourceType.rule, configurations); }; export const loadConfiguration = (configurationId: string) => { @@ -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); diff --git a/packages/sonarwhal/tests/helpers/rule-runner.ts b/packages/sonarwhal/tests/helpers/rule-runner.ts index fb3a855af59..da0de88e444 100644 --- a/packages/sonarwhal/tests/helpers/rule-runner.ts +++ b/packages/sonarwhal/tests/helpers/rule-runner.ts @@ -172,7 +172,7 @@ export const testRule = (ruleId: string, ruleTests: Array, 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) => { diff --git a/packages/sonarwhal/tests/lib/sonarwhal.ts b/packages/sonarwhal/tests/lib/sonarwhal.ts index 8265c13a22a..dc19fa341ca 100644 --- a/packages/sonarwhal/tests/lib/sonarwhal.ts +++ b/packages/sonarwhal/tests/lib/sonarwhal.ts @@ -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: [], @@ -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: [], @@ -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: [], @@ -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: [], @@ -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: [], @@ -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: [], @@ -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: [], @@ -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: [], @@ -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: [], @@ -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: [], diff --git a/packages/sonarwhal/tests/lib/utils/resource-loader.ts b/packages/sonarwhal/tests/lib/utils/resource-loader.ts index b7e5a7d9352..2d967a3e9f1 100644 --- a/packages/sonarwhal/tests/lib/utils/resource-loader.ts +++ b/packages/sonarwhal/tests/lib/utils/resource-loader.ts @@ -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'); @@ -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`); }); @@ -183,6 +183,7 @@ test('loadResources loads all the resources of a given config', (t) => { name: 'jsdom', options: {} }, + extends: [], formatters: ['json'], ignoredUrls: [], parsers: [],