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

Commit

Permalink
More intuitive configuration composition (#1622)
Browse files Browse the repository at this point in the history
  • Loading branch information
adidahiya authored and nchen63 committed Nov 10, 2016
1 parent cbdf7a8 commit e736ba9
Show file tree
Hide file tree
Showing 6 changed files with 64 additions and 53 deletions.
51 changes: 26 additions & 25 deletions src/configuration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -170,14 +170,14 @@ export function loadConfigurationFromPath(configFilePath: string): IConfiguratio
const configFileDir = path.dirname(resolvedConfigFilePath);

configFile.rulesDirectory = getRulesDirectories(configFile.rulesDirectory, configFileDir);
configFile.extends = arrayify(configFile.extends);
// load configurations, in order, using their identifiers or relative paths
// apply the current configuration last by placing it last in this array
const configs = arrayify(configFile.extends).map((name) => {
const nextConfigFilePath = resolveConfigurationPath(name, configFileDir);
return loadConfigurationFromPath(nextConfigFilePath);
}).concat([configFile]);

for (const name of configFile.extends) {
const baseConfigFilePath = resolveConfigurationPath(name, configFileDir);
const baseConfigFile = loadConfigurationFromPath(baseConfigFilePath);
configFile = extendConfigurationFile(configFile, baseConfigFile);
}
return configFile;
return configs.reduce(extendConfigurationFile, {});
}
}

Expand Down Expand Up @@ -211,28 +211,29 @@ function resolveConfigurationPath(filePath: string, relativeTo?: string) {
}
}

export function extendConfigurationFile(config: IConfigurationFile, baseConfig: IConfigurationFile): IConfigurationFile {
export function extendConfigurationFile(targetConfig: IConfigurationFile,
nextConfigSource: IConfigurationFile): IConfigurationFile {
let combinedConfig: IConfigurationFile = {};

const baseRulesDirectory = arrayify(baseConfig.rulesDirectory);
const configRulesDirectory = arrayify(config.rulesDirectory);
combinedConfig.rulesDirectory = configRulesDirectory.concat(baseRulesDirectory);
const configRulesDirectory = arrayify(targetConfig.rulesDirectory);
const nextConfigRulesDirectory = arrayify(nextConfigSource.rulesDirectory);
combinedConfig.rulesDirectory = configRulesDirectory.concat(nextConfigRulesDirectory);

combinedConfig.rules = {};
for (const name of Object.keys(objectify(baseConfig.rules))) {
combinedConfig.rules[name] = baseConfig.rules[name];
}
for (const name of Object.keys(objectify(config.rules))) {
combinedConfig.rules[name] = config.rules[name];
}
const combineProperties = (targetProperty: any, nextProperty: any) => {
let combinedProperty: any = {};
for (const name of Object.keys(objectify(targetProperty))) {
combinedProperty[name] = targetProperty[name];
}
// next config source overwrites the target config object
for (const name of Object.keys(objectify(nextProperty))) {
combinedProperty[name] = nextProperty[name];
}
return combinedProperty;
};

combinedConfig.jsRules = {};
for (const name of Object.keys(objectify(baseConfig.jsRules))) {
combinedConfig.jsRules[name] = baseConfig.jsRules[name];
}
for (const name of Object.keys(objectify(config.jsRules))) {
combinedConfig.jsRules[name] = config.jsRules[name];
}
combinedConfig.rules = combineProperties(targetConfig.rules, nextConfigSource.rules);
combinedConfig.jsRules = combineProperties(targetConfig.jsRules, nextConfigSource.jsRules);
combinedConfig.linterOptions = combineProperties(targetConfig.linterOptions, nextConfigSource.linterOptions);

return combinedConfig;
}
Expand Down
6 changes: 6 additions & 0 deletions src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@
* limitations under the License.
*/

/**
* Enforces the invariant that the input is an array.
*/
export function arrayify<T>(arg: T | T[]): T[] {
if (Array.isArray(arg)) {
return arg;
Expand All @@ -25,6 +28,9 @@ export function arrayify<T>(arg: T | T[]): T[] {
}
}

/**
* Enforces the invariant that the input is an object.
*/
export function objectify(arg: any): any {
if (typeof arg === "object" && arg != null) {
return arg;
Expand Down
6 changes: 4 additions & 2 deletions test/config/tslint-custom-rules-with-two-dirs.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,12 @@
"rulesDirectory": ["../files/custom-rules-2", "../files/custom-rules/"],
"jsRules": {
"always-fail": true,
"no-fail": true
"no-fail": true,
"rule-two": true
},
"rules": {
"always-fail": true,
"no-fail": true
"no-fail": true,
"rule-two": true
}
}
39 changes: 24 additions & 15 deletions test/configurationTests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ describe("Configuration", () => {
it("extendConfigurationFile", () => {
const EMPTY_CONFIG: IConfigurationFile = {
jsRules: {},
linterOptions: {},
rules: {},
rulesDirectory: [],
};
Expand All @@ -32,72 +33,80 @@ describe("Configuration", () => {
assert.deepEqual(extendConfigurationFile(EMPTY_CONFIG, {}), EMPTY_CONFIG);
assert.deepEqual(extendConfigurationFile({}, {
jsRules: { row: "oar" },
linterOptions: {},
rules: { foo: "bar" },
rulesDirectory: "foo",
}), {
jsRules: { row: "oar" },
linterOptions: {},
rules: {foo: "bar"},
rulesDirectory: ["foo"],
});
assert.deepEqual(extendConfigurationFile({
const actualConfig = extendConfigurationFile({
jsRules: { row: "oar" },
linterOptions: {},
rules: {
a: 1,
b: 2,
b: 1,
},
rulesDirectory: ["foo", "bar"],
}, {
jsRules: { fly: "wings" },
linterOptions: {},
rules: {
b: 1,
b: 2,
c: 3,
},
rulesDirectory: "baz",
}), {
});
/* tslint:disable:object-literal-sort-keys */
const expectedConfig = {
jsRules: {
fly: "wings",
row: "oar",
fly: "wings",
},
linterOptions: {},
rules: {
a: 1,
b: 2,
c: 3,
},
rulesDirectory: ["foo", "bar", "baz"],
});
};
assert.deepEqual(actualConfig, expectedConfig);
});

describe("loadConfigurationFromPath", () => {
it("extends with relative path", () => {
let config = loadConfigurationFromPath("./test/config/tslint-extends-relative.json");
const config = loadConfigurationFromPath("./test/config/tslint-extends-relative.json");

assert.isArray(config.rulesDirectory);
assert.isTrue(config.rules["no-fail"]);
assert.isFalse(config.rules["always-fail"]);
assert.isTrue(config.rules["no-fail"], "did not pick up 'no-fail' in base config");
assert.isFalse(config.rules["always-fail"], "did not set 'always-fail' in top config");
assert.isTrue(config.jsRules["no-fail"]);
assert.isFalse(config.jsRules["always-fail"]);
});

it("extends with package", () => {
let config = loadConfigurationFromPath("./test/config/tslint-extends-package.json");
const config = loadConfigurationFromPath("./test/config/tslint-extends-package.json");

assert.isArray(config.rulesDirectory);
/* tslint:disable:object-literal-sort-keys */
assert.deepEqual(config.jsRules, {
"rule-one": true,
"rule-two": true,
"rule-three": false,
"rule-two": true,
});
assert.deepEqual(config.rules, {
"rule-one": true,
"rule-two": true,
"rule-three": false,
"rule-two": true,
});
/* tslint:enable:object-literal-sort-keys */
});

it("extends with package without customization", () => {
let config = loadConfigurationFromPath("./test/config/tslint-extends-package-no-mod.json");
const config = loadConfigurationFromPath("./test/config/tslint-extends-package-no-mod.json");

assert.isArray(config.rulesDirectory);
assert.deepEqual(config.jsRules, {
Expand Down Expand Up @@ -171,13 +180,13 @@ describe("Configuration", () => {
"always-fail": false,
"no-fail": true,
"rule-one": true,
"rule-two": false,
"rule-two": true,
});
assert.deepEqual(config.rules, {
"always-fail": false,
"no-fail": true,
"rule-one": true,
"rule-two": false,
"rule-two": true,
});
});

Expand Down
8 changes: 0 additions & 8 deletions test/executable/executableTests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -106,14 +106,6 @@ describe("Executable", function() {
done();
});
});

it("exits with code 2 if several custom rules directories are specified in config file and file contains lint errors", (done) => {
execCli(["-c", "./test/config/tslint-custom-rules-with-two-dirs.json", "src/tslint.ts"], (err) => {
assert.isNotNull(err, "process should exit with error");
assert.strictEqual(err.code, 2, "error code should be 2");
done();
});
});
});

describe("--fix flag", () => {
Expand Down
7 changes: 4 additions & 3 deletions test/ruleLoaderTests.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
/*
/**
* @license
* Copyright 2013 Palantir Technologies, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
Expand Down Expand Up @@ -40,7 +41,7 @@ describe("Rule Loader", () => {

assert.throws(
() => loadRules(invalidConfiguration, {}, RULES_DIRECTORY),
/invalidConfig1\ninvalidConfig2/
/invalidConfig1\ninvalidConfig2/,
);
});

Expand All @@ -56,7 +57,7 @@ describe("Rule Loader", () => {

assert.throws(
() => loadRules(invalidConfiguration, {}, RULES_DIRECTORY),
/_indent\nforin_\n-quotemark\neofline-/
/_indent\nforin_\n-quotemark\neofline-/,
);
});

Expand Down

0 comments on commit e736ba9

Please sign in to comment.