diff --git a/README.md b/README.md index 18cd6f9a..973f5caa 100755 --- a/README.md +++ b/README.md @@ -8,6 +8,7 @@ [![CircleCI](https://circleci.com/gh/tclindner/npm-package-json-lint.svg?style=svg)](https://circleci.com/gh/tclindner/npm-package-json-lint) [![Dependency Status](https://david-dm.org/tclindner/npm-package-json-lint.svg?style=flat-square)](https://david-dm.org/tclindner/npm-package-json-lint) [![devDependency Status](https://david-dm.org/tclindner/npm-package-json-lint/dev-status.svg?style=flat-square)](https://david-dm.org/tclindner/npm-package-json-lint#info=devDependencies) +[![Netlify Status](https://api.netlify.com/api/v1/badges/e76a30d9-13f0-4691-a49b-454570589de2/deploy-status)](https://app.netlify.com/sites/npmpackagejsonlint/deploys) ## What is npm-package-json-lint? diff --git a/jest.config.js b/jest.config.js index 3a76a6c2..f5375c18 100755 --- a/jest.config.js +++ b/jest.config.js @@ -1,13 +1,13 @@ module.exports = { clearMocks: true, collectCoverage: true, - collectCoverageFrom: ['src/**/*.js'], + collectCoverageFrom: ['src/**/*.js', '!src/cli.js'], coverageThreshold: { global: { - branches: 87, - functions: 91, - lines: 92, - statements: 92 + branches: 97, + functions: 100, + lines: 99, + statements: 99 } }, restoreMocks: true, diff --git a/package.json b/package.json index 4d9bd1e5..6cc16702 100755 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "npm-package-json-lint", - "version": "4.0.0-beta.2", + "version": "4.0.0-beta.3", "description": "Configurable linter for package.json files.", "keywords": [ "lint", @@ -35,28 +35,28 @@ "test:ci": "jest --runInBand" }, "dependencies": { - "ajv": "^6.10.0", + "ajv": "^6.10.2", "ajv-errors": "^1.0.1", "chalk": "^2.4.2", "cosmiconfig": "^5.2.1", "debug": "^4.1.1", "globby": "^10.0.1", - "ignore": "^5.1.2", + "ignore": "^5.1.4", "is-plain-obj": "^2.0.0", "log-symbols": "^3.0.0", "meow": "^5.0.0", "plur": "^3.1.1", - "semver": "^6.1.1", + "semver": "^6.3.0", "strip-json-comments": "^3.0.1" }, "devDependencies": { "eslint": "^5.16.0", "eslint-config-tc": "^6.5.0", "eslint-formatter-pretty": "^2.1.1", - "eslint-plugin-import": "^2.17.3", - "eslint-plugin-prettier": "^3.1.0", + "eslint-plugin-import": "^2.18.2", + "eslint-plugin-prettier": "^3.1.1", "figures": "^3.0.0", - "jest": "^24.8.0", + "jest": "^24.9.0", "npm-package-json-lint-config-default": "^2.0.0", "npm-package-json-lint-config-tc": "^2.2.0", "prettier": "^1.18.2" diff --git a/src/Config.js b/src/Config.js index 994dab37..6a110c9d 100644 --- a/src/Config.js +++ b/src/Config.js @@ -1,7 +1,7 @@ const debug = require('debug')('npm-package-json-lint:Config'); const cosmiconfig = require('cosmiconfig'); -// const ConfigValidator = require('./config/ConfigValidator'); +const configValidator = require('./config/ConfigValidator'); const cosmicConfigTransformer = require('./config/cosmicConfigTransformer'); const applyExtendsIfSpecified = require('./config/applyExtendsIfSpecified'); const applyOverrides = require('./config/applyOverrides'); @@ -20,8 +20,9 @@ class Config { * @param {Object} config The user passed config object. * @param {string} configFile The user passed configFile path. * @param {string} configBaseDirectory The base directory that config should be pulled from. + * @param {Object} rules Rules object */ - constructor(cwd, config, configFile, configBaseDirectory) { + constructor(cwd, config, configFile, configBaseDirectory, rules) { if (config) { this.config = applyExtendsIfSpecified(config, 'PassedConfig'); } @@ -29,6 +30,7 @@ class Config { this.cwd = cwd; this.configFile = configFile; this.configBaseDirectory = configBaseDirectory; + this.rules = rules; this.explorer = cosmiconfig('npmpackagejsonlint', { transform: cosmicConfigTransformer.transform(cwd, configBaseDirectory) }); @@ -77,7 +79,7 @@ class Config { debug(`Overrides applied for ${filePath}`); - // ConfigValidator.validateRules(config, 'cli', this.linter); + configValidator.validateRules(config, 'cli', this.rules); return config; } diff --git a/src/NpmPackageJsonLint.js b/src/NpmPackageJsonLint.js index e38b0afb..8f1399cd 100644 --- a/src/NpmPackageJsonLint.js +++ b/src/NpmPackageJsonLint.js @@ -100,14 +100,10 @@ class NpmPackageJsonLint { this.version = pkg.version; - // if (this.options.rules && Object.keys(this.options.rules).length) { - // ConfigValidator.validateRules(this.options.rules, 'cli', this.linter); - // } - - this.configHelper = new Config(this.cwd, config, configFile, configBaseDirectory); - this.rules = new Rules(); this.rules.load(); + + this.configHelper = new Config(this.cwd, config, configFile, configBaseDirectory, this.rules); } /** diff --git a/src/config/ConfigValidator.js b/src/config/ConfigValidator.js index e3748d98..01200717 100755 --- a/src/config/ConfigValidator.js +++ b/src/config/ConfigValidator.js @@ -106,44 +106,41 @@ const validateRule = (ruleModule, ruleName, userConfig, source) => { }; /** - * Public ConfigValidator class - * @class + * Validates only the rules of a config object + * + * @param {Object} rulesConfig The rules config object to validate. + * @param {String} source The name of the configuration source to report in any errors. + * @param {Object} rules Rules object + * @returns {undefined} No return + * @static */ -class ConfigValidator { - /** - * Validates entire config object, including top-level properties. - * - * @param {Object} config The config object to validate. - * @param {String} source The name of the configuration source to report in any errors. - * @param {NpmPackageJsonLint} linterContext Linter context - * @returns {undefined} No return - * @static - */ - static validate(config, source, linterContext) { - ConfigSchema.isConfigObjectSchemaValid(config, source); - ConfigValidator.validateRules(config.rules, source, linterContext); +const validateRules = (rulesConfig, source, rules) => { + if (!rulesConfig) { + return; } - /** - * Validates only the rules of a config object - * - * @param {Object} rulesConfig The rules config object to validate. - * @param {String} source The name of the configuration source to report in any errors. - * @param {NpmPackageJsonLint} linterContext Linter context - * @returns {undefined} No return - * @static - */ - static validateRules(rulesConfig, source, linterContext) { - if (!rulesConfig) { - return; - } + Object.keys(rulesConfig).forEach(ruleName => { + const ruleModule = rules.get(ruleName); - Object.keys(rulesConfig).forEach(ruleName => { - const ruleModule = linterContext.getRule(ruleName); + validateRule(ruleModule, ruleName, rulesConfig[ruleName], source); + }); +}; - validateRule(ruleModule, ruleName, rulesConfig[ruleName], source); - }); - } -} +/** + * Validates entire config object, including top-level properties. + * + * @param {Object} config The config object to validate. + * @param {String} source The name of the configuration source to report in any errors. + * @param {Object} rules Rules object + * @returns {undefined} No return + * @static + */ +const validate = (config, source, rules) => { + ConfigSchema.isConfigObjectSchemaValid(config, source); + validateRules(config.rules, source, rules); +}; -module.exports = ConfigValidator; +module.exports = { + validate, + validateRules +}; diff --git a/test/fixtures/extendsLocalInvalid/.npmpackagejsonlintrc.md b/test/fixtures/extendsLocalInvalid/.npmpackagejsonlintrc.md new file mode 100644 index 00000000..ebbb175f --- /dev/null +++ b/test/fixtures/extendsLocalInvalid/.npmpackagejsonlintrc.md @@ -0,0 +1,6 @@ +{ + "extends": "./npmpackagejsonlint.config.js", + "rules": { + "require-author": "error" + } +} diff --git a/test/unit/cli.test.js b/test/integration/cli.test.js old mode 100755 new mode 100644 similarity index 99% rename from test/unit/cli.test.js rename to test/integration/cli.test.js index e7c17a93..60f1f915 --- a/test/unit/cli.test.js +++ b/test/integration/cli.test.js @@ -38,7 +38,7 @@ const threeRunTimeException = 3; const {env} = process; env.FORCE_COLOR = 0; -describe('cli Unit Tests', () => { +describe('cli Integration Tests', () => { describe('when the help command is run', () => { const expected = ` Configurable linter for package.json files. diff --git a/test/unit/Config.test.js b/test/unit/Config.test.js index 32732cb1..acd8573c 100755 --- a/test/unit/Config.test.js +++ b/test/unit/Config.test.js @@ -2,6 +2,10 @@ const cosmiconfig = require('cosmiconfig'); const Config = require('./../../src/Config'); const applyOverrides = require('../../src/config/applyOverrides'); const applyExtendsIfSpecified = require('../../src/config/applyExtendsIfSpecified'); +const Rules = require('../../src/Rules'); + +const rules = new Rules(); +rules.load(); jest.mock('cosmiconfig'); jest.mock('../../src/config/applyOverrides'); @@ -17,11 +21,9 @@ describe('Config Unit Tests', () => { const configBaseDirectory = ''; const loadSyncMock = jest.fn().mockReturnValue({ - rules: { - 'require-version': 'error', - 'require-name': 'error', - 'require-scripts': 'error' - } + 'require-version': 'error', + 'require-name': 'error', + 'require-scripts': 'error' }); const searchSyncMock = jest.fn(); @@ -32,14 +34,12 @@ describe('Config Unit Tests', () => { }; }); - const configObj = new Config(cwd, config, configFile, configBaseDirectory); + const configObj = new Config(cwd, config, configFile, configBaseDirectory, rules); const expectedConfigObj = { - rules: { - 'require-version': 'error', - 'require-name': 'error', - 'require-scripts': 'error' - } + 'require-version': 'error', + 'require-name': 'error', + 'require-scripts': 'error' }; const filePath = './package.json'; const result = configObj.getConfigForFile(filePath); @@ -62,11 +62,9 @@ describe('Config Unit Tests', () => { const loadSyncMock = jest.fn(); const searchSyncMock = jest.fn().mockReturnValue({ - rules: { - 'require-version': 'error', - 'require-name': 'error', - 'require-scripts': 'error' - } + 'require-version': 'error', + 'require-name': 'error', + 'require-scripts': 'error' }); cosmiconfig.mockImplementation(() => { @@ -76,14 +74,12 @@ describe('Config Unit Tests', () => { }; }); - const configObj = new Config(cwd, config, configFile, configBaseDirectory); + const configObj = new Config(cwd, config, configFile, configBaseDirectory, rules); const expectedConfigObj = { - rules: { - 'require-version': 'error', - 'require-name': 'error', - 'require-scripts': 'error' - } + 'require-version': 'error', + 'require-name': 'error', + 'require-scripts': 'error' }; const filePath = './package.json'; const result = configObj.getConfigForFile(filePath); @@ -114,7 +110,7 @@ describe('Config Unit Tests', () => { }; }); - const configObj = new Config(cwd, config, configFile, configBaseDirectory); + const configObj = new Config(cwd, config, configFile, configBaseDirectory, rules); const filePath = './package.json'; @@ -141,7 +137,7 @@ describe('Config Unit Tests', () => { }; }); - const configObj = new Config(cwd, config, configFile, configBaseDirectory); + const configObj = new Config(cwd, config, configFile, configBaseDirectory, rules); const filePath = './package.json'; @@ -202,7 +198,7 @@ describe('Config Unit Tests', () => { 'require-scripts': 'error' }); - const configObj = new Config(cwd, config, configFile, configBaseDirectory); + const configObj = new Config(cwd, config, configFile, configBaseDirectory, rules); const expectedConfigObj = { 'require-version': 'error', diff --git a/test/unit/api.test.js b/test/unit/api.test.js new file mode 100644 index 00000000..0adf3026 --- /dev/null +++ b/test/unit/api.test.js @@ -0,0 +1,9 @@ +const api = require('../../src/api'); + +jest.mock('../../src/NpmPackageJsonLint'); + +describe('api Unit Tests', () => { + test('NpmPackageJsonLint should be exported', () => { + expect(api).toHaveProperty('NpmPackageJsonLint'); + }); +}); diff --git a/test/unit/config/ConfigValidator.test.js b/test/unit/config/ConfigValidator.test.js index 7c9dd84e..89846ab0 100755 --- a/test/unit/config/ConfigValidator.test.js +++ b/test/unit/config/ConfigValidator.test.js @@ -1,17 +1,17 @@ -const ConfigValidator = require('./../../../src/config/ConfigValidator'); -const NpmPackageJsonLint = require('./../../../src/NpmPackageJsonLint'); +const configValidator = require('../../../src/config/ConfigValidator'); +const Rules = require('../../../src/Rules'); -// const linterContext = new NpmPackageJsonLint(); -const linterContext = null; +const rules = new Rules(); +rules.load(); -describe.skip('ConfigValidator Unit Tests', () => { +describe('configValidator Unit Tests', () => { describe('validateRules method', () => { describe('when called with null rulesConfig', () => { test('undefined should be returned', () => { const ruleConfig = null; const source = 'cli'; - const actual = ConfigValidator.validateRules(ruleConfig, source, linterContext); + const actual = configValidator.validateRules(ruleConfig, source, rules); expect(actual).toBeUndefined(); }); }); @@ -25,7 +25,7 @@ describe.skip('ConfigValidator Unit Tests', () => { const source = 'cli'; expect(() => { - ConfigValidator.validateRules(ruleConfig, source, linterContext); + configValidator.validateRules(ruleConfig, source, rules); }).toThrow( 'cli:\n\tConfiguration for rule "valid-values-author" is invalid:\n\t- severity must be a string.\n\t- severity must be either "off", "warning", or "error".' ); @@ -40,7 +40,7 @@ describe.skip('ConfigValidator Unit Tests', () => { const source = 'cli'; expect(() => { - ConfigValidator.validateRules(ruleConfig, source, linterContext); + configValidator.validateRules(ruleConfig, source, rules); }).toThrow( 'cli:\n\tConfiguration for rule "valid-values-author" is invalid:\n\t- the second item in an array rule config must be an array.' ); @@ -55,7 +55,7 @@ describe.skip('ConfigValidator Unit Tests', () => { const source = 'cli'; expect(() => { - ConfigValidator.validateRules(ruleConfig, source, linterContext); + configValidator.validateRules(ruleConfig, source, rules); }).toThrow( 'cli:\n\tConfiguration for rule "valid-values-author" is invalid:\n\t- the second item in an array rule config must have at least 1 item.' ); @@ -70,7 +70,7 @@ describe.skip('ConfigValidator Unit Tests', () => { const source = 'cli'; expect(() => { - ConfigValidator.validateRules(ruleConfig, source, linterContext); + configValidator.validateRules(ruleConfig, source, rules); }).toThrow( 'cli:\n\tConfiguration for rule "valid-values-author" is invalid:\n\t- the second item in an array rule config must have unique items.' ); @@ -85,7 +85,7 @@ describe.skip('ConfigValidator Unit Tests', () => { const source = 'cli'; expect(() => { - ConfigValidator.validateRules(ruleConfig, source, linterContext); + configValidator.validateRules(ruleConfig, source, rules); }).toThrow( 'cli:\n\tConfiguration for rule "valid-values-author" is invalid:\n\t- rule config must be an array, e.g. ["error", ["value1", "value2"]].' ); @@ -100,7 +100,7 @@ describe.skip('ConfigValidator Unit Tests', () => { const source = 'cli'; expect(() => { - ConfigValidator.validateRules(ruleConfig, source, linterContext); + configValidator.validateRules(ruleConfig, source, rules); }).toThrow( 'cli:\n\tConfiguration for rule "valid-values-author" is invalid:\n\t- array rules must have two items, severity and options array. e.g. ["error", ["value1", "value2"]].' ); @@ -115,7 +115,7 @@ describe.skip('ConfigValidator Unit Tests', () => { const source = 'cli'; expect(() => { - ConfigValidator.validateRules(ruleConfig, source, linterContext); + configValidator.validateRules(ruleConfig, source, rules); }).toThrow( 'cli:\n\tConfiguration for rule "valid-values-author" is invalid:\n\t- array rules must have two items, severity and options array. e.g. ["error", ["value1", "value2"]].\n\t- array rules are only allowed two items, severity and the list is values. e.g. ["error", ["value1", "value2"]].' ); @@ -129,7 +129,7 @@ describe.skip('ConfigValidator Unit Tests', () => { }; const source = 'cli'; - ConfigValidator.validateRules(ruleConfig, source, linterContext); + configValidator.validateRules(ruleConfig, source, rules); }); }); @@ -140,7 +140,7 @@ describe.skip('ConfigValidator Unit Tests', () => { }; const source = 'cli'; - ConfigValidator.validateRules(ruleConfig, source, linterContext); + configValidator.validateRules(ruleConfig, source, rules); }); }); @@ -152,7 +152,7 @@ describe.skip('ConfigValidator Unit Tests', () => { const source = 'cli'; expect(() => { - ConfigValidator.validateRules(ruleConfig, source, linterContext); + configValidator.validateRules(ruleConfig, source, rules); }).toThrow( 'cli:\n\tConfiguration for rule "valid-values-author" is invalid:\n\t- is an array type rule. It must be set to "off" if an array is not supplied.' ); @@ -169,7 +169,7 @@ describe.skip('ConfigValidator Unit Tests', () => { const source = 'cli'; expect(() => { - ConfigValidator.validateRules(ruleConfig, source, linterContext); + configValidator.validateRules(ruleConfig, source, rules); }).toThrow( 'cli:\n\tConfiguration for rule "description-format" is invalid:\n\t- severity must be a string.\n\t- severity must be either "off", "warning", or "error".' ); @@ -184,7 +184,7 @@ describe.skip('ConfigValidator Unit Tests', () => { const source = 'cli'; expect(() => { - ConfigValidator.validateRules(ruleConfig, source, linterContext); + configValidator.validateRules(ruleConfig, source, rules); }).toThrow( 'cli:\n\tConfiguration for rule "description-format" is invalid:\n\t- the second item in an object rule config must be an object.' ); @@ -199,7 +199,7 @@ describe.skip('ConfigValidator Unit Tests', () => { const source = 'cli'; expect(() => { - ConfigValidator.validateRules(ruleConfig, source, linterContext); + configValidator.validateRules(ruleConfig, source, rules); }).toThrow( 'cli:\n\tConfiguration for rule "description-format" is invalid:\n\t- rule config must be an array, e.g. ["error", {}].' ); @@ -214,7 +214,7 @@ describe.skip('ConfigValidator Unit Tests', () => { const source = 'cli'; expect(() => { - ConfigValidator.validateRules(ruleConfig, source, linterContext); + configValidator.validateRules(ruleConfig, source, rules); }).toThrow( 'cli:\n\tConfiguration for rule "description-format" is invalid:\n\t- object rules must have two items, severity and options object. e.g. ["error", {}].' ); @@ -229,7 +229,7 @@ describe.skip('ConfigValidator Unit Tests', () => { const source = 'cli'; expect(() => { - ConfigValidator.validateRules(ruleConfig, source, linterContext); + configValidator.validateRules(ruleConfig, source, rules); }).toThrow( 'cli:\n\tConfiguration for rule "description-format" is invalid:\n\t- object rules must have two items, severity and options object. e.g. ["error", {}].\n\t- object rules are only allowed two items, severity and options object. e.g. ["error", {}].' ); @@ -243,7 +243,7 @@ describe.skip('ConfigValidator Unit Tests', () => { }; const source = 'cli'; - ConfigValidator.validateRules(ruleConfig, source, linterContext); + configValidator.validateRules(ruleConfig, source, rules); }); }); @@ -254,7 +254,7 @@ describe.skip('ConfigValidator Unit Tests', () => { }; const source = 'cli'; - ConfigValidator.validateRules(ruleConfig, source, linterContext); + configValidator.validateRules(ruleConfig, source, rules); }); }); @@ -266,7 +266,7 @@ describe.skip('ConfigValidator Unit Tests', () => { const source = 'cli'; expect(() => { - ConfigValidator.validateRules(ruleConfig, source, linterContext); + configValidator.validateRules(ruleConfig, source, rules); }).toThrow( 'cli:\n\tConfiguration for rule "description-format" is invalid:\n\t- is an object type rule. It must be set to "off" if an object is not supplied.' ); @@ -283,7 +283,7 @@ describe.skip('ConfigValidator Unit Tests', () => { const source = 'cli'; expect(() => { - ConfigValidator.validateRules(ruleConfig, source, linterContext); + configValidator.validateRules(ruleConfig, source, rules); }).toThrow( 'cli:\n\tConfiguration for rule "prefer-no-version-zero-dependencies" is invalid:\n\t- severity must be either "off", "warning", or "error".' ); @@ -298,7 +298,7 @@ describe.skip('ConfigValidator Unit Tests', () => { const source = 'cli'; expect(() => { - ConfigValidator.validateRules(ruleConfig, source, linterContext); + configValidator.validateRules(ruleConfig, source, rules); }).toThrow( 'cli:\n\tConfiguration for rule "prefer-no-version-zero-dependencies" is invalid:\n\t- severity must be either "off", "warning", or "error".' ); @@ -313,7 +313,7 @@ describe.skip('ConfigValidator Unit Tests', () => { const source = 'cli'; expect(() => { - ConfigValidator.validateRules(ruleConfig, source, linterContext); + configValidator.validateRules(ruleConfig, source, rules); }).toThrow( 'cli:\n\tConfiguration for rule "prefer-no-version-zero-dependencies" is invalid:\n\t- the second item in an object rule config must be an object.' ); @@ -328,7 +328,7 @@ describe.skip('ConfigValidator Unit Tests', () => { const source = 'cli'; expect(() => { - ConfigValidator.validateRules(ruleConfig, source, linterContext); + configValidator.validateRules(ruleConfig, source, rules); }).toThrow( 'cli:\n\tConfiguration for rule "prefer-no-version-zero-dependencies" is invalid:\n\t- expections must be an array.' ); @@ -343,7 +343,7 @@ describe.skip('ConfigValidator Unit Tests', () => { const source = 'cli'; expect(() => { - ConfigValidator.validateRules(ruleConfig, source, linterContext); + configValidator.validateRules(ruleConfig, source, rules); }).toThrow( 'cli:\n\tConfiguration for rule "prefer-no-version-zero-dependencies" is invalid:\n\t- expections must have at least 1 item.' ); @@ -358,7 +358,7 @@ describe.skip('ConfigValidator Unit Tests', () => { const source = null; expect(() => { - ConfigValidator.validateRules(ruleConfig, source, linterContext); + configValidator.validateRules(ruleConfig, source, rules); }).toThrow( 'Configuration for rule "prefer-no-version-zero-dependencies" is invalid:\n\t- expections must have unique items.' ); @@ -373,7 +373,7 @@ describe.skip('ConfigValidator Unit Tests', () => { const source = 'cli'; expect(() => { - ConfigValidator.validateRules(ruleConfig, source, linterContext); + configValidator.validateRules(ruleConfig, source, rules); }).toThrow( 'cli:\n\tConfiguration for rule "prefer-no-version-zero-dependencies" is invalid:\n\t- each exception must be a string.' ); @@ -388,7 +388,7 @@ describe.skip('ConfigValidator Unit Tests', () => { const source = 'cli'; expect(() => { - ConfigValidator.validateRules(ruleConfig, source, linterContext); + configValidator.validateRules(ruleConfig, source, rules); }).toThrow( 'cli:\n\tConfiguration for rule "prefer-no-version-zero-dependencies" is invalid:\n\t- rule config must be an array, e.g. ["error", {}].' ); @@ -403,7 +403,7 @@ describe.skip('ConfigValidator Unit Tests', () => { const source = 'cli'; expect(() => { - ConfigValidator.validateRules(ruleConfig, source, linterContext); + configValidator.validateRules(ruleConfig, source, rules); }).toThrow( 'cli:\n\tConfiguration for rule "prefer-no-version-zero-dependencies" is invalid:\n\t- object rules must have two items, severity and options object. e.g. ["error", {}].' ); @@ -418,7 +418,7 @@ describe.skip('ConfigValidator Unit Tests', () => { const source = 'cli'; expect(() => { - ConfigValidator.validateRules(ruleConfig, source, linterContext); + configValidator.validateRules(ruleConfig, source, rules); }).toThrow( 'cli:\n\tConfiguration for rule "prefer-no-version-zero-dependencies" is invalid:\n\t- object rules must have two items, severity and options object. e.g. ["error", {}].\n\t- object rules are only allowed two items, severity and options object. e.g. ["error", {}].' ); @@ -432,7 +432,7 @@ describe.skip('ConfigValidator Unit Tests', () => { }; const source = 'cli'; - ConfigValidator.validateRules(ruleConfig, source, linterContext); + configValidator.validateRules(ruleConfig, source, rules); }); }); @@ -443,7 +443,7 @@ describe.skip('ConfigValidator Unit Tests', () => { }; const source = 'cli'; - ConfigValidator.validateRules(ruleConfig, source, linterContext); + configValidator.validateRules(ruleConfig, source, rules); }); }); @@ -454,7 +454,7 @@ describe.skip('ConfigValidator Unit Tests', () => { }; const source = 'cli'; - ConfigValidator.validateRules(ruleConfig, source, linterContext); + configValidator.validateRules(ruleConfig, source, rules); }); }); }); @@ -467,7 +467,7 @@ describe.skip('ConfigValidator Unit Tests', () => { }; const source = 'cli'; - ConfigValidator.validateRules(ruleConfig, source, linterContext); + configValidator.validateRules(ruleConfig, source, rules); }); }); @@ -478,7 +478,7 @@ describe.skip('ConfigValidator Unit Tests', () => { }; const source = 'cli'; - ConfigValidator.validateRules(ruleConfig, source, linterContext); + configValidator.validateRules(ruleConfig, source, rules); }); }); @@ -489,7 +489,7 @@ describe.skip('ConfigValidator Unit Tests', () => { }; const source = 'cli'; - ConfigValidator.validateRules(ruleConfig, source, linterContext); + configValidator.validateRules(ruleConfig, source, rules); }); }); @@ -501,7 +501,7 @@ describe.skip('ConfigValidator Unit Tests', () => { const source = 'cli'; expect(() => { - ConfigValidator.validateRules(ruleConfig, source, linterContext); + configValidator.validateRules(ruleConfig, source, rules); }).toThrow( 'cli:\n\tConfiguration for rule "require-author" is invalid:\n\t- severity must be a string.\n\t- severity must be either "off", "warning", or "error".' ); @@ -517,7 +517,7 @@ describe.skip('ConfigValidator Unit Tests', () => { const source = 'cli'; expect(() => { - ConfigValidator.validateRules(ruleConfig, source, linterContext); + configValidator.validateRules(ruleConfig, source, rules); }).toThrow( 'cli:\n\tConfiguration for rule "require-author" is invalid:\n\t- severity must be a string.\n\t- severity must be either "off", "warning", or "error".' ); @@ -535,9 +535,9 @@ describe.skip('ConfigValidator Unit Tests', () => { root: true }; const source = 'cli'; - jest.spyOn(ConfigValidator, 'validateRules').mockReturnValue(true); + jest.spyOn(configValidator, 'validateRules').mockReturnValue(true); - ConfigValidator.validate(config, source, linterContext); + configValidator.validate(config, source, rules); }); test('extends and rules only for top level items, an exception should not be thrown', () => { @@ -546,9 +546,9 @@ describe.skip('ConfigValidator Unit Tests', () => { rules: {} }; const source = 'cli'; - jest.spyOn(ConfigValidator, 'validateRules').mockReturnValue(true); + jest.spyOn(configValidator, 'validateRules').mockReturnValue(true); - ConfigValidator.validate(config, source, linterContext); + configValidator.validate(config, source, rules); }); test('extends and root only for top level items, an exception should not be thrown', () => { @@ -557,9 +557,9 @@ describe.skip('ConfigValidator Unit Tests', () => { root: true }; const source = 'cli'; - jest.spyOn(ConfigValidator, 'validateRules').mockReturnValue(true); + jest.spyOn(configValidator, 'validateRules').mockReturnValue(true); - ConfigValidator.validate(config, source, linterContext); + configValidator.validate(config, source, rules); }); test('rules and root for top level items, an exception should not be thrown', () => { @@ -568,9 +568,9 @@ describe.skip('ConfigValidator Unit Tests', () => { root: true }; const source = 'cli'; - jest.spyOn(ConfigValidator, 'validateRules').mockReturnValue(true); + jest.spyOn(configValidator, 'validateRules').mockReturnValue(true); - ConfigValidator.validate(config, source, linterContext); + configValidator.validate(config, source, rules); }); test('extends as array items, an exception should not be thrown', () => { @@ -580,9 +580,9 @@ describe.skip('ConfigValidator Unit Tests', () => { root: true }; const source = 'cli'; - jest.spyOn(ConfigValidator, 'validateRules').mockReturnValue(true); + jest.spyOn(configValidator, 'validateRules').mockReturnValue(true); - ConfigValidator.validate(config, source, linterContext); + configValidator.validate(config, source, rules); }); }); @@ -594,10 +594,10 @@ describe.skip('ConfigValidator Unit Tests', () => { root: true }; const source = 'cli'; - jest.spyOn(ConfigValidator, 'validateRules').mockReturnValue(true); + jest.spyOn(configValidator, 'validateRules').mockReturnValue(true); expect(() => { - ConfigValidator.validate(config, source, linterContext); + configValidator.validate(config, source, rules); }).toThrow( 'npm-package-json-lint configuration in cli is invalid:\n\t- extends must be either a string or an array of strings.\n' ); @@ -610,10 +610,10 @@ describe.skip('ConfigValidator Unit Tests', () => { root: true }; const source = 'cli'; - jest.spyOn(ConfigValidator, 'validateRules').mockReturnValue(true); + jest.spyOn(configValidator, 'validateRules').mockReturnValue(true); expect(() => { - ConfigValidator.validate(config, source, linterContext); + configValidator.validate(config, source, rules); }).toThrow( 'npm-package-json-lint configuration in cli is invalid:\n\t- extends must have at least one item if it is an array.\n' ); @@ -626,10 +626,10 @@ describe.skip('ConfigValidator Unit Tests', () => { root: true }; const source = 'cli'; - jest.spyOn(ConfigValidator, 'validateRules').mockReturnValue(true); + jest.spyOn(configValidator, 'validateRules').mockReturnValue(true); expect(() => { - ConfigValidator.validate(config, source, linterContext); + configValidator.validate(config, source, rules); }).toThrow( 'npm-package-json-lint configuration in cli is invalid:\n\t- extends must have unique items if it is an array.\n' ); @@ -642,10 +642,10 @@ describe.skip('ConfigValidator Unit Tests', () => { root: true }; const source = 'cli'; - jest.spyOn(ConfigValidator, 'validateRules').mockReturnValue(true); + jest.spyOn(configValidator, 'validateRules').mockReturnValue(true); expect(() => { - ConfigValidator.validate(config, source, linterContext); + configValidator.validate(config, source, rules); }).toThrow('npm-package-json-lint configuration in cli is invalid:\n\t- rules must be an object.\n'); }); @@ -656,20 +656,20 @@ describe.skip('ConfigValidator Unit Tests', () => { root: 'true' }; const source = 'cli'; - jest.spyOn(ConfigValidator, 'validateRules').mockReturnValue(true); + jest.spyOn(configValidator, 'validateRules').mockReturnValue(true); expect(() => { - ConfigValidator.validate(config, source, linterContext); + configValidator.validate(config, source, rules); }).toThrow('npm-package-json-lint configuration in cli is invalid:\n\t- root must be a boolean.\n'); }); test('config is a string, an error should be thrown', () => { const config = 'my config'; const source = 'cli'; - jest.spyOn(ConfigValidator, 'validateRules').mockReturnValue(true); + jest.spyOn(configValidator, 'validateRules').mockReturnValue(true); expect(() => { - ConfigValidator.validate(config, source, linterContext); + configValidator.validate(config, source, rules); }).toThrow( 'npm-package-json-lint configuration in cli is invalid:\n\t- npm-package-json-lint config should be an object.\n' ); @@ -683,32 +683,14 @@ describe.skip('ConfigValidator Unit Tests', () => { extraProp: true }; const source = 'cli'; - jest.spyOn(ConfigValidator, 'validateRules').mockReturnValue(true); + jest.spyOn(configValidator, 'validateRules').mockReturnValue(true); expect(() => { - ConfigValidator.validate(config, source, linterContext); + configValidator.validate(config, source, rules); }).toThrow( 'npm-package-json-lint configuration in cli is invalid:\n\t- npm-package-json-lint config has unexpected top-level property. Valid properties include: `extends`, `rules`, and `root`.\n' ); }); }); - - describe('when validate is called with a valid schema and invalid rules', () => { - test('an error should be thrown', () => { - const config = { - extends: 'value', - rules: {}, - root: true - }; - const source = 'cli'; - jest.spyOn(ConfigValidator, 'validateRules').mockImplementation(() => { - throw new Error(); - }); - - expect(() => { - ConfigValidator.validate(config, source, linterContext); - }).toThrow(); - }); - }); }); }); diff --git a/test/unit/config/applyExtendsIfSpecified.test.js b/test/unit/config/applyExtendsIfSpecified.test.js index 1040a027..7e084bef 100755 --- a/test/unit/config/applyExtendsIfSpecified.test.js +++ b/test/unit/config/applyExtendsIfSpecified.test.js @@ -1,7 +1,7 @@ const applyExtendsIfSpecified = require('../../../src/config/applyExtendsIfSpecified'); describe('applyExtendsIfSpecified Unit Tests', () => { - test('when file has local extends (valid), a config object is returned', () => { + test('when file has local extends (valid - js), a config object is returned', () => { const expectedConfigObj = { extends: './test/fixtures/extendsLocal/npmpackagejsonlint.config.js', rules: { @@ -30,6 +30,35 @@ describe('applyExtendsIfSpecified Unit Tests', () => { expect(result).toStrictEqual(expectedConfigObj); }); + test('when file has local extends (valid - json), a config object is returned', () => { + const expectedConfigObj = { + extends: './test/fixtures/extendsLocal/.npmpackagejsonlintrc.json', + rules: { + 'require-author': 'error', + 'require-description': 'error' + }, + overrides: [ + { + patterns: ['**/package.json'], + rules: { + 'require-author': 'warning' + } + } + ] + }; + const passedConfig = { + extends: './test/fixtures/extendsLocal/.npmpackagejsonlintrc.json', + rules: { + 'require-author': 'error' + } + }; + + const filePath = './test/fixtures/extendsLocal/package.json'; + const result = applyExtendsIfSpecified(passedConfig, filePath); + + expect(result).toStrictEqual(expectedConfigObj); + }); + test('when file has local extends (invalid), a config object is returned', () => { const passedConfig = { extends: './npmpackagejsonlint.config.js', @@ -44,6 +73,20 @@ describe('applyExtendsIfSpecified Unit Tests', () => { }).toThrow(); }); + test('when file has local extends (invalid extension type), a config object is returned', () => { + const passedConfig = { + extends: './npmpackagejsonlintrc.md', + rules: { + 'require-author': 'error' + } + }; + const filePath = './test/fixtures/extendsLocalInvalid/package.json'; + + expect(() => { + applyExtendsIfSpecified(passedConfig, filePath); + }).toThrow(); + }); + test('when file has module extends (valid), a config object is returned', () => { const expectedConfigObj = { extends: 'npm-package-json-lint-config-default', diff --git a/test/unit/config/cosmicConfigTransformer.test.js b/test/unit/config/cosmicConfigTransformer.test.js new file mode 100755 index 00000000..dee6f2b4 --- /dev/null +++ b/test/unit/config/cosmicConfigTransformer.test.js @@ -0,0 +1,84 @@ +const path = require('path'); +const cosmicConfigTransformer = require('../../../src/config/cosmicConfigTransformer'); +const applyExtendsIfSpecified = require('../../../src/config/applyExtendsIfSpecified'); +const applyOverrides = require('../../../src/config/applyOverrides'); + +jest.mock('path'); +jest.mock('../../../src/config/applyExtendsIfSpecified'); +jest.mock('../../../src/config/applyOverrides'); + +describe('cosmicConfigTransformer Unit Tests', () => { + describe('transform method', () => { + describe('no cosmiconfigResult', () => { + test('null should be returned', () => { + const cwd = 'cwd'; + const configBaseDirectory = 'configBaseDirectory'; + + const transformer = cosmicConfigTransformer.transform(cwd, configBaseDirectory); + const actual = transformer(null); + expect(actual).toBeNull(); + }); + }); + + describe('valid cosmiconfigResult - no configBaseDirectory', () => { + test('null should be returned', () => { + path.dirname.mockReturnValue('./myConfig'); + applyExtendsIfSpecified.mockReturnValue({ + rules: 'rules', + overrides: 'overrides' + }); + applyOverrides.mockReturnValue('appliedOverrides'); + + const cwd = 'cwd'; + const configBaseDirectory = 'configBaseDirectory'; + + const transformer = cosmicConfigTransformer.transform(cwd, configBaseDirectory); + const cosmiconfigResult = { + config: { + property: 'value' + }, + filepath: 'myFilePath' + }; + const actual = transformer(cosmiconfigResult); + expect(actual).toStrictEqual('appliedOverrides'); + + expect(path.dirname).toHaveBeenCalledTimes(0); + expect(applyExtendsIfSpecified).toHaveBeenCalledTimes(1); + expect(applyExtendsIfSpecified).toHaveBeenCalledWith(cosmiconfigResult.config, cosmiconfigResult.filepath); + expect(applyOverrides).toHaveBeenCalledTimes(1); + expect(applyOverrides).toHaveBeenCalledWith(cwd, cosmiconfigResult.filepath, 'rules', 'overrides'); + }); + }); + + describe('valid cosmiconfigResult - no configBaseDirectory', () => { + test('null should be returned', () => { + path.dirname.mockReturnValue('./myConfig'); + applyExtendsIfSpecified.mockReturnValue({ + rules: 'rules', + overrides: 'overrides' + }); + applyOverrides.mockReturnValue('appliedOverrides'); + + const cwd = 'cwd'; + const configBaseDirectory = null; + + const transformer = cosmicConfigTransformer.transform(cwd, configBaseDirectory); + const cosmiconfigResult = { + config: { + property: 'value' + }, + filepath: 'myFilePath' + }; + const actual = transformer(cosmiconfigResult); + expect(actual).toStrictEqual('appliedOverrides'); + + expect(path.dirname).toHaveBeenCalledTimes(1); + expect(path.dirname).toHaveBeenCalledWith(cosmiconfigResult.filepath); + expect(applyExtendsIfSpecified).toHaveBeenCalledTimes(1); + expect(applyExtendsIfSpecified).toHaveBeenCalledWith(cosmiconfigResult.config, cosmiconfigResult.filepath); + expect(applyOverrides).toHaveBeenCalledTimes(1); + expect(applyOverrides).toHaveBeenCalledWith(cwd, cosmiconfigResult.filepath, 'rules', 'overrides'); + }); + }); + }); +});