From a1df9564e907d6a3288573f02e97cf5d1552ec9d Mon Sep 17 00:00:00 2001 From: miripiruni Date: Sat, 23 Nov 2019 12:39:23 +0700 Subject: [PATCH 1/4] Add no-git-dependencies and no-git-devDependencies rules --- .../rules/dependencies/no-git-dependencies.md | 187 ++++++++++++++ .../dependencies/no-git-devDependencies.md | 187 ++++++++++++++ src/rules/no-git-dependencies.js | 20 ++ src/rules/no-git-devDependencies.js | 20 ++ src/validators/dependency-audit.js | 79 +++++- test/unit/rules/no-git-dependencies.test.js | 237 ++++++++++++++++++ .../unit/rules/no-git-devDependencies.test.js | 237 ++++++++++++++++++ test/unit/validators/dependency-audit.test.js | 114 +++++++++ 8 files changed, 1080 insertions(+), 1 deletion(-) create mode 100644 docs/rules/dependencies/no-git-dependencies.md create mode 100644 docs/rules/dependencies/no-git-devDependencies.md create mode 100644 src/rules/no-git-dependencies.js create mode 100644 src/rules/no-git-devDependencies.js create mode 100644 test/unit/rules/no-git-dependencies.test.js create mode 100644 test/unit/rules/no-git-devDependencies.test.js diff --git a/docs/rules/dependencies/no-git-dependencies.md b/docs/rules/dependencies/no-git-dependencies.md new file mode 100644 index 00000000..a8b68247 --- /dev/null +++ b/docs/rules/dependencies/no-git-dependencies.md @@ -0,0 +1,187 @@ +--- +id: no-git-dependencies +title: no-git-dependencies +--- + +Enabling this rule will result in an error being generated if one of the dependencies in `dependencies` uses git repository. + +## Example .npmpackagejsonlintrc configuration + +```json +{ + "rules": { + "no-git-dependencies": "error" + } +} +``` + +With exceptions + +```json +{ + "rules": { + "no-git-dependencies": ["error", { + "exceptions": ["myModule"] + }] + } +} +``` + +## Rule Details + +### *Incorrect* examples + +```json +{ + "dependencies": { + "grunt-npm-package-json-lint": "github:miripiruni/grunt-npm-package-json-lint" + } +} +``` + +```json +{ + "dependencies": { + "grunt-npm-package-json-lint": "miripiruni/grunt-npm-package-json-lint" + } +} +``` + +```json +{ + "dependencies": { + "grunt-npm-package-json-lint": "miripiruni/grunt-npm-package-json-lint#miri/issue-42" + } +} +``` + +```json +{ + "dependencies": { + "grunt-npm-package-json-lint": "miripiruni/grunt-npm-package-json-lint#v1.0.0-rc-1" + } +} +``` + +```json +{ + "dependencies": { + "grunt-npm-package-json-lint": "miripiruni/grunt-npm-package-json-lint#4f9012b132aa4d2d6097b516b31327c999b0a846" + } +} +``` + +```json +{ + "dependencies": { + "grunt-npm-package-json-lint": "git://github.com/miripiruni/grunt-npm-package-json-lint.git" + } +} +``` + +```json +{ + "dependencies": { + "grunt-npm-package-json-lint": "git@github.com:miripiruni/grunt-npm-package-json-lint.git" + } +} +``` + +```json +{ + "dependencies": { + "grunt-npm-package-json-lint": "git+https://github.com/miripiruni/grunt-npm-package-json-lint.git" + } +} +``` + +```json +{ + "dependencies": { + "grunt-npm-package-json-lint": "git+ssh://github.com/miripiruni/grunt-npm-package-json-lint.git" + } +} +``` + + +```json +{ + "dependencies": { + "grunt-npm-package-json-lint": "http://github.com/miripiruni/grunt-npm-package-json-lint.git" + } +} +``` + +```json +{ + "dependencies": { + "grunt-npm-package-json-lint": "https://github.com/miripiruni/grunt-npm-package-json-lint.git" + } +} +``` + + + +### *Correct* example(s) + +```json +{ + "dependencies": { + "gulp-npm-package-json-lint": "4.0.0" + } +} +``` + +```json +{ + "dependencies": { + "gulp-npm-package-json-lint": "^4.0.0" + } +} +``` + +```json +{ + "dependencies": { + "gulp-npm-package-json-lint": "~4.0.0" + } +} +``` + +```json +{ + "dependencies": { + "gulp-npm-package-json-lint": ">=4.0.0" + } +} +``` + +```json +{ + "dependencies": { + "gulp-npm-package-json-lint": "<=4.0.0" + } +} +``` + +```json +{ + "dependencies": { + "gulp-npm-package-json-lint": "*" + } +} +``` + +## Shorthand for disabling the rule in .npmpackagejsonlintrc configuration + +```json +{ + "rules": { + "no-git-dependencies": "off" + } +} +``` + +## History + +* Introduced in version 4.3.0 diff --git a/docs/rules/dependencies/no-git-devDependencies.md b/docs/rules/dependencies/no-git-devDependencies.md new file mode 100644 index 00000000..2ea2227c --- /dev/null +++ b/docs/rules/dependencies/no-git-devDependencies.md @@ -0,0 +1,187 @@ +--- +id: no-git-devDependencies +title: no-git-devDependencies +--- + +Enabling this rule will result in an error being generated if one of the devDependencies in `devDependencies` uses git repository. + +## Example .npmpackagejsonlintrc configuration + +```json +{ + "rules": { + "no-git-devDependencies": "error" + } +} +``` + +With exceptions + +```json +{ + "rules": { + "no-git-devDependencies": ["error", { + "exceptions": ["myModule"] + }] + } +} +``` + +## Rule Details + +### *Incorrect* examples + +```json +{ + "devDependencies": { + "grunt-npm-package-json-lint": "github:miripiruni/grunt-npm-package-json-lint" + } +} +``` + +```json +{ + "devDependencies": { + "grunt-npm-package-json-lint": "miripiruni/grunt-npm-package-json-lint" + } +} +``` + +```json +{ + "devDependencies": { + "grunt-npm-package-json-lint": "miripiruni/grunt-npm-package-json-lint#miri/issue-42" + } +} +``` + +```json +{ + "devDependencies": { + "grunt-npm-package-json-lint": "miripiruni/grunt-npm-package-json-lint#v1.0.0-rc-1" + } +} +``` + +```json +{ + "devDependencies": { + "grunt-npm-package-json-lint": "miripiruni/grunt-npm-package-json-lint#4f9012b132aa4d2d6097b516b31327c999b0a846" + } +} +``` + +```json +{ + "devDependencies": { + "grunt-npm-package-json-lint": "git://github.com/miripiruni/grunt-npm-package-json-lint.git" + } +} +``` + +```json +{ + "devDependencies": { + "grunt-npm-package-json-lint": "git@github.com:miripiruni/grunt-npm-package-json-lint.git" + } +} +``` + +```json +{ + "devDependencies": { + "grunt-npm-package-json-lint": "git+https://github.com/miripiruni/grunt-npm-package-json-lint.git" + } +} +``` + +```json +{ + "devDependencies": { + "grunt-npm-package-json-lint": "git+ssh://github.com/miripiruni/grunt-npm-package-json-lint.git" + } +} +``` + + +```json +{ + "devDependencies": { + "grunt-npm-package-json-lint": "http://github.com/miripiruni/grunt-npm-package-json-lint.git" + } +} +``` + +```json +{ + "devDependencies": { + "grunt-npm-package-json-lint": "https://github.com/miripiruni/grunt-npm-package-json-lint.git" + } +} +``` + + + +### *Correct* example(s) + +```json +{ + "devDependencies": { + "gulp-npm-package-json-lint": "4.0.0" + } +} +``` + +```json +{ + "devDependencies": { + "gulp-npm-package-json-lint": "^4.0.0" + } +} +``` + +```json +{ + "devDependencies": { + "gulp-npm-package-json-lint": "~4.0.0" + } +} +``` + +```json +{ + "devDependencies": { + "gulp-npm-package-json-lint": ">=4.0.0" + } +} +``` + +```json +{ + "devDependencies": { + "gulp-npm-package-json-lint": "<=4.0.0" + } +} +``` + +```json +{ + "devDependencies": { + "gulp-npm-package-json-lint": "*" + } +} +``` + +## Shorthand for disabling the rule in .npmpackagejsonlintrc configuration + +```json +{ + "rules": { + "no-git-devDependencies": "off" + } +} +``` + +## History + +* Introduced in version 4.3.0 diff --git a/src/rules/no-git-dependencies.js b/src/rules/no-git-dependencies.js new file mode 100644 index 00000000..33971eda --- /dev/null +++ b/src/rules/no-git-dependencies.js @@ -0,0 +1,20 @@ +const {doVersContainGitRepository} = require('./../validators/dependency-audit'); +const LintIssue = require('./../LintIssue'); + +const lintId = 'no-git-dependencies'; +const nodeName = 'dependencies'; +const message = 'You are using dependencies from git repository. Please use dependencies from npm.'; +const ruleType = 'optionalObject'; + +const lint = (packageJsonData, severity, config) => { + if (packageJsonData.hasOwnProperty(nodeName) && doVersContainGitRepository(packageJsonData, nodeName, config)) { + return new LintIssue(lintId, severity, nodeName, message); + } + + return true; +}; + +module.exports = { + lint, + ruleType +}; diff --git a/src/rules/no-git-devDependencies.js b/src/rules/no-git-devDependencies.js new file mode 100644 index 00000000..f1946426 --- /dev/null +++ b/src/rules/no-git-devDependencies.js @@ -0,0 +1,20 @@ +const {doVersContainGitRepository} = require('./../validators/dependency-audit'); +const LintIssue = require('./../LintIssue'); + +const lintId = 'no-git-devDependencies'; +const nodeName = 'devDependencies'; +const message = 'You are using devDependencies from git repository. Please use devDependencies from npm.'; +const ruleType = 'optionalObject'; + +const lint = (packageJsonData, severity, config) => { + if (packageJsonData.hasOwnProperty(nodeName) && doVersContainGitRepository(packageJsonData, nodeName, config)) { + return new LintIssue(lintId, severity, nodeName, message); + } + + return true; +}; + +module.exports = { + lint, + ruleType +}; diff --git a/src/validators/dependency-audit.js b/src/validators/dependency-audit.js index 13762f51..3cd32c5e 100755 --- a/src/validators/dependency-audit.js +++ b/src/validators/dependency-audit.js @@ -225,6 +225,82 @@ const doVersContainNonAbsolute = (packageJsonData, nodeName, config) => { return dependenciesChecked > 0 ? !onlyAbsoluteVersionDetected : false; }; +/** + * Determines whether or not dependency versions are git repository + * @param {object} packageJsonData Valid JSON + * @param {string} nodeName Name of a node in the package.json file + * @param {object} config Rule configuration + * @return {boolean} True if the package has an git repo. + */ +const doVersContainGitRepository = (packageJsonData, nodeName, config) => { + for (const dependencyName in packageJsonData[nodeName]) { + if (hasExceptions(config) && config.exceptions.includes(dependencyName)) { + continue; + } + + const dependencyVersion = packageJsonData[nodeName][dependencyName]; + + if (isGitRepositoryUrl(dependencyVersion) || isGithubRepositoryShortcut(dependencyVersion)) { + return true; + } + } + + return false; +}; + +/** + * Determines whether or not version is git repository url + * @param version value of package's version + * @return {boolean} True if the version is an git repo url. + */ +const isGitRepositoryUrl = (version) => { + if (isArchiveUrl(version)) { + return false; + } + + // based on https://github.com/npm/hosted-git-info + const protocols = new Set([ + 'git@', + 'git://', + 'git+https://', + 'git+ssh://', + 'http://', + 'https://' + ]); + + let match = false; + + for (let protocol of protocols) { + if (version.startsWith(protocol)) { + match = true; + break; + } + } + + return match; +}; + +/** + * Determines whether or not version is url to archive + * @param version value of package's version + * @return {boolean} True if the version is url to archive + */ +const isArchiveUrl = (version) => { + return version.endsWith('.tar.gz') || version.endsWith('.zip'); +}; + +const GITHUB_SHORTCUT_URL = /^(github:)?[^\/]+\/[^\/]+/; + +/** + * Determines whether or not version is a shortcut to github repository + * @param version value of package's version + * @return {boolean} True if the version is a shortcut to github repository + */ +const isGithubRepositoryShortcut = (version) => { + return GITHUB_SHORTCUT_URL.test(version); +}; + + module.exports = { hasDependency, hasDepPrereleaseVers, @@ -233,5 +309,6 @@ module.exports = { areVersRangesValid, doVersContainInvalidRange, areVersionsAbsolute, - doVersContainNonAbsolute + doVersContainNonAbsolute, + doVersContainGitRepository }; diff --git a/test/unit/rules/no-git-dependencies.test.js b/test/unit/rules/no-git-dependencies.test.js new file mode 100644 index 00000000..c2ac3f60 --- /dev/null +++ b/test/unit/rules/no-git-dependencies.test.js @@ -0,0 +1,237 @@ +const ruleModule = require('./../../../src/rules/no-git-dependencies'); + +const {lint, ruleType} = ruleModule; + +describe('no-git-dependencies Unit Tests', () => { + describe('a rule type value should be exported', () => { + test('it should equal "optionalObject"', () => { + expect(ruleType).toStrictEqual('optionalObject'); + }); + }); + + describe('when package.json has node with an invalid value', () => { + describe('with github:user/repo', () => { + test('LintIssue object should be returned', () => { + const packageJsonData = { + dependencies: { + 'test': 'github:username/repo' + } + }; + const response = lint(packageJsonData, 'error', {exceptions: ['grunt-npm-package-json-lint']}); + + expect(response.lintId).toStrictEqual('no-git-dependencies'); + expect(response.severity).toStrictEqual('error'); + expect(response.node).toStrictEqual('dependencies'); + expect(response.lintMessage).toStrictEqual( + 'You are using dependencies from git repository. Please use dependencies from npm.' + ); + }); + }); + + describe('with user/repo', () => { + test('LintIssue object should be returned', () => { + const packageJsonData = { + dependencies: { + 'test': 'username/repo' + } + }; + const response = lint(packageJsonData, 'error', {exceptions: ['grunt-npm-package-json-lint']}); + + expect(response.lintId).toStrictEqual('no-git-dependencies'); + expect(response.severity).toStrictEqual('error'); + expect(response.node).toStrictEqual('dependencies'); + expect(response.lintMessage).toStrictEqual( + 'You are using dependencies from git repository. Please use dependencies from npm.' + ); + }); + }); + + describe('with user/repo#author/issue', () => { + test('LintIssue object should be returned', () => { + const packageJsonData = { + dependencies: { + 'test': 'username/repo#author/issue' + } + }; + const response = lint(packageJsonData, 'error', {exceptions: ['grunt-npm-package-json-lint']}); + + expect(response.lintId).toStrictEqual('no-git-dependencies'); + expect(response.severity).toStrictEqual('error'); + expect(response.node).toStrictEqual('dependencies'); + expect(response.lintMessage).toStrictEqual( + 'You are using dependencies from git repository. Please use dependencies from npm.' + ); + }); + }); + + describe('with user/repo#tag', () => { + test('LintIssue object should be returned', () => { + const packageJsonData = { + dependencies: { + 'test': 'username/repo#v1.0.0-rc-1' + } + }; + const response = lint(packageJsonData, 'error', {exceptions: ['grunt-npm-package-json-lint']}); + + expect(response.lintId).toStrictEqual('no-git-dependencies'); + expect(response.severity).toStrictEqual('error'); + expect(response.node).toStrictEqual('dependencies'); + expect(response.lintMessage).toStrictEqual( + 'You are using dependencies from git repository. Please use dependencies from npm.' + ); + }); + }); + + describe('with user/repo#commit', () => { + test('LintIssue object should be returned', () => { + const packageJsonData = { + dependencies: { + 'test': 'username/repo#4f9012b132aa4d2d6097b516b31327c999b0a846' + } + }; + const response = lint(packageJsonData, 'error', {exceptions: ['grunt-npm-package-json-lint']}); + + expect(response.lintId).toStrictEqual('no-git-dependencies'); + expect(response.severity).toStrictEqual('error'); + expect(response.node).toStrictEqual('dependencies'); + expect(response.lintMessage).toStrictEqual( + 'You are using dependencies from git repository. Please use dependencies from npm.' + ); + }); + }); + + describe('with git://github.com/user/repo.git', () => { + test('LintIssue object should be returned', () => { + const packageJsonData = { + dependencies: { + 'test': 'git://github.com/user/repo.git' + } + }; + const response = lint(packageJsonData, 'error', {exceptions: ['grunt-npm-package-json-lint']}); + + expect(response.lintId).toStrictEqual('no-git-dependencies'); + expect(response.severity).toStrictEqual('error'); + expect(response.node).toStrictEqual('dependencies'); + expect(response.lintMessage).toStrictEqual( + 'You are using dependencies from git repository. Please use dependencies from npm.' + ); + }); + }); + + describe('with git@github.com:user/repo.git', () => { + test('LintIssue object should be returned', () => { + const packageJsonData = { + dependencies: { + 'test': 'git@github.com:user/repo.git' + } + }; + const response = lint(packageJsonData, 'error', {exceptions: ['grunt-npm-package-json-lint']}); + + expect(response.lintId).toStrictEqual('no-git-dependencies'); + expect(response.severity).toStrictEqual('error'); + expect(response.node).toStrictEqual('dependencies'); + expect(response.lintMessage).toStrictEqual( + 'You are using dependencies from git repository. Please use dependencies from npm.' + ); + }); + }); + + describe('with git+https://github.com/user/repo.git', () => { + test('LintIssue object should be returned', () => { + const packageJsonData = { + dependencies: { + 'test': 'git+https://github.com/user/repo.git' + } + }; + const response = lint(packageJsonData, 'error', {exceptions: ['grunt-npm-package-json-lint']}); + + expect(response.lintId).toStrictEqual('no-git-dependencies'); + expect(response.severity).toStrictEqual('error'); + expect(response.node).toStrictEqual('dependencies'); + expect(response.lintMessage).toStrictEqual( + 'You are using dependencies from git repository. Please use dependencies from npm.' + ); + }); + }); + + describe('with git+ssh://github.com/user/repo.git', () => { + test('LintIssue object should be returned', () => { + const packageJsonData = { + dependencies: { + 'test': 'git+ssh://github.com/user/repo.git' + } + }; + const response = lint(packageJsonData, 'error', {exceptions: ['grunt-npm-package-json-lint']}); + + expect(response.lintId).toStrictEqual('no-git-dependencies'); + expect(response.severity).toStrictEqual('error'); + expect(response.node).toStrictEqual('dependencies'); + expect(response.lintMessage).toStrictEqual( + 'You are using dependencies from git repository. Please use dependencies from npm.' + ); + }); + }); + + describe('with http://github.com/user/repo.git', () => { + test('LintIssue object should be returned', () => { + const packageJsonData = { + dependencies: { + 'test': 'http://github.com/user/repo.git' + } + }; + const response = lint(packageJsonData, 'error', {exceptions: ['grunt-npm-package-json-lint']}); + + expect(response.lintId).toStrictEqual('no-git-dependencies'); + expect(response.severity).toStrictEqual('error'); + expect(response.node).toStrictEqual('dependencies'); + expect(response.lintMessage).toStrictEqual( + 'You are using dependencies from git repository. Please use dependencies from npm.' + ); + }); + }); + + describe('with https://github.com/user/repo.git', () => { + test('LintIssue object should be returned', () => { + const packageJsonData = { + dependencies: { + 'test': 'https://github.com/user/repo.git' + } + }; + const response = lint(packageJsonData, 'error', {exceptions: ['grunt-npm-package-json-lint']}); + + expect(response.lintId).toStrictEqual('no-git-dependencies'); + expect(response.severity).toStrictEqual('error'); + expect(response.node).toStrictEqual('dependencies'); + expect(response.lintMessage).toStrictEqual( + 'You are using dependencies from git repository. Please use dependencies from npm.' + ); + }); + }); + }); + + describe('when package.json has node with a invalid value and config exception', () => { + test('true should be returned', () => { + const packageJsonData = { + dependencies: { + 'my-module': 'username/repo' + } + }; + const response = lint(packageJsonData, 'error', {exceptions: ['my-module']}); + + expect(response).toBe(true); + }); + }); + + describe('when package.json has node with a valid value', () => { + test('true should be returned', () => { + const packageJsonData = { + dependencies: { + 'my-module': '^1.2.3' + } + }; + const response = lint(packageJsonData, 'error'); + + expect(response).toBe(true); + }); + }); +}); diff --git a/test/unit/rules/no-git-devDependencies.test.js b/test/unit/rules/no-git-devDependencies.test.js new file mode 100644 index 00000000..1caea3b4 --- /dev/null +++ b/test/unit/rules/no-git-devDependencies.test.js @@ -0,0 +1,237 @@ +const ruleModule = require('./../../../src/rules/no-git-devDependencies'); + +const {lint, ruleType} = ruleModule; + +describe('no-git-devDependencies Unit Tests', () => { + describe('a rule type value should be exported', () => { + test('it should equal "optionalObject"', () => { + expect(ruleType).toStrictEqual('optionalObject'); + }); + }); + + describe('when package.json has node with an invalid value', () => { + describe('with github:user/repo', () => { + test('LintIssue object should be returned', () => { + const packageJsonData = { + devDependencies: { + 'test': 'github:username/repo' + } + }; + const response = lint(packageJsonData, 'error', {exceptions: ['grunt-npm-package-json-lint']}); + + expect(response.lintId).toStrictEqual('no-git-devDependencies'); + expect(response.severity).toStrictEqual('error'); + expect(response.node).toStrictEqual('devDependencies'); + expect(response.lintMessage).toStrictEqual( + 'You are using devDependencies from git repository. Please use devDependencies from npm.' + ); + }); + }); + + describe('with user/repo', () => { + test('LintIssue object should be returned', () => { + const packageJsonData = { + devDependencies: { + 'test': 'username/repo' + } + }; + const response = lint(packageJsonData, 'error', {exceptions: ['grunt-npm-package-json-lint']}); + + expect(response.lintId).toStrictEqual('no-git-devDependencies'); + expect(response.severity).toStrictEqual('error'); + expect(response.node).toStrictEqual('devDependencies'); + expect(response.lintMessage).toStrictEqual( + 'You are using devDependencies from git repository. Please use devDependencies from npm.' + ); + }); + }); + + describe('with user/repo#author/issue', () => { + test('LintIssue object should be returned', () => { + const packageJsonData = { + devDependencies: { + 'test': 'username/repo#author/issue' + } + }; + const response = lint(packageJsonData, 'error', {exceptions: ['grunt-npm-package-json-lint']}); + + expect(response.lintId).toStrictEqual('no-git-devDependencies'); + expect(response.severity).toStrictEqual('error'); + expect(response.node).toStrictEqual('devDependencies'); + expect(response.lintMessage).toStrictEqual( + 'You are using devDependencies from git repository. Please use devDependencies from npm.' + ); + }); + }); + + describe('with user/repo#tag', () => { + test('LintIssue object should be returned', () => { + const packageJsonData = { + devDependencies: { + 'test': 'username/repo#v1.0.0-rc-1' + } + }; + const response = lint(packageJsonData, 'error', {exceptions: ['grunt-npm-package-json-lint']}); + + expect(response.lintId).toStrictEqual('no-git-devDependencies'); + expect(response.severity).toStrictEqual('error'); + expect(response.node).toStrictEqual('devDependencies'); + expect(response.lintMessage).toStrictEqual( + 'You are using devDependencies from git repository. Please use devDependencies from npm.' + ); + }); + }); + + describe('with user/repo#commit', () => { + test('LintIssue object should be returned', () => { + const packageJsonData = { + devDependencies: { + 'test': 'username/repo#4f9012b132aa4d2d6097b516b31327c999b0a846' + } + }; + const response = lint(packageJsonData, 'error', {exceptions: ['grunt-npm-package-json-lint']}); + + expect(response.lintId).toStrictEqual('no-git-devDependencies'); + expect(response.severity).toStrictEqual('error'); + expect(response.node).toStrictEqual('devDependencies'); + expect(response.lintMessage).toStrictEqual( + 'You are using devDependencies from git repository. Please use devDependencies from npm.' + ); + }); + }); + + describe('with git://github.com/user/repo.git', () => { + test('LintIssue object should be returned', () => { + const packageJsonData = { + devDependencies: { + 'test': 'git://github.com/user/repo.git' + } + }; + const response = lint(packageJsonData, 'error', {exceptions: ['grunt-npm-package-json-lint']}); + + expect(response.lintId).toStrictEqual('no-git-devDependencies'); + expect(response.severity).toStrictEqual('error'); + expect(response.node).toStrictEqual('devDependencies'); + expect(response.lintMessage).toStrictEqual( + 'You are using devDependencies from git repository. Please use devDependencies from npm.' + ); + }); + }); + + describe('with git@github.com:user/repo.git', () => { + test('LintIssue object should be returned', () => { + const packageJsonData = { + devDependencies: { + 'test': 'git@github.com:user/repo.git' + } + }; + const response = lint(packageJsonData, 'error', {exceptions: ['grunt-npm-package-json-lint']}); + + expect(response.lintId).toStrictEqual('no-git-devDependencies'); + expect(response.severity).toStrictEqual('error'); + expect(response.node).toStrictEqual('devDependencies'); + expect(response.lintMessage).toStrictEqual( + 'You are using devDependencies from git repository. Please use devDependencies from npm.' + ); + }); + }); + + describe('with git+https://github.com/user/repo.git', () => { + test('LintIssue object should be returned', () => { + const packageJsonData = { + devDependencies: { + 'test': 'git+https://github.com/user/repo.git' + } + }; + const response = lint(packageJsonData, 'error', {exceptions: ['grunt-npm-package-json-lint']}); + + expect(response.lintId).toStrictEqual('no-git-devDependencies'); + expect(response.severity).toStrictEqual('error'); + expect(response.node).toStrictEqual('devDependencies'); + expect(response.lintMessage).toStrictEqual( + 'You are using devDependencies from git repository. Please use devDependencies from npm.' + ); + }); + }); + + describe('with git+ssh://github.com/user/repo.git', () => { + test('LintIssue object should be returned', () => { + const packageJsonData = { + devDependencies: { + 'test': 'git+ssh://github.com/user/repo.git' + } + }; + const response = lint(packageJsonData, 'error', {exceptions: ['grunt-npm-package-json-lint']}); + + expect(response.lintId).toStrictEqual('no-git-devDependencies'); + expect(response.severity).toStrictEqual('error'); + expect(response.node).toStrictEqual('devDependencies'); + expect(response.lintMessage).toStrictEqual( + 'You are using devDependencies from git repository. Please use devDependencies from npm.' + ); + }); + }); + + describe('with http://github.com/user/repo.git', () => { + test('LintIssue object should be returned', () => { + const packageJsonData = { + devDependencies: { + 'test': 'http://github.com/user/repo.git' + } + }; + const response = lint(packageJsonData, 'error', {exceptions: ['grunt-npm-package-json-lint']}); + + expect(response.lintId).toStrictEqual('no-git-devDependencies'); + expect(response.severity).toStrictEqual('error'); + expect(response.node).toStrictEqual('devDependencies'); + expect(response.lintMessage).toStrictEqual( + 'You are using devDependencies from git repository. Please use devDependencies from npm.' + ); + }); + }); + + describe('with https://github.com/user/repo.git', () => { + test('LintIssue object should be returned', () => { + const packageJsonData = { + devDependencies: { + 'test': 'https://github.com/user/repo.git' + } + }; + const response = lint(packageJsonData, 'error', {exceptions: ['grunt-npm-package-json-lint']}); + + expect(response.lintId).toStrictEqual('no-git-devDependencies'); + expect(response.severity).toStrictEqual('error'); + expect(response.node).toStrictEqual('devDependencies'); + expect(response.lintMessage).toStrictEqual( + 'You are using devDependencies from git repository. Please use devDependencies from npm.' + ); + }); + }); + }); + + describe('when package.json has node with a invalid value and config exception', () => { + test('true should be returned', () => { + const packageJsonData = { + devDependencies: { + 'my-module': 'username/repo' + } + }; + const response = lint(packageJsonData, 'error', {exceptions: ['my-module']}); + + expect(response).toBe(true); + }); + }); + + describe('when package.json has node with a valid value', () => { + test('true should be returned', () => { + const packageJsonData = { + devDependencies: { + 'my-module': '^1.2.3' + } + }; + const response = lint(packageJsonData, 'error'); + + expect(response).toBe(true); + }); + }); +}); diff --git a/test/unit/validators/dependency-audit.test.js b/test/unit/validators/dependency-audit.test.js index 5c61ff96..cae31642 100755 --- a/test/unit/validators/dependency-audit.test.js +++ b/test/unit/validators/dependency-audit.test.js @@ -744,6 +744,120 @@ describe('dependency-audit Unit Tests', () => { }); }); + describe('doVersContainGitRepository method', () => { + describe('when the node exists in the package.json file', () => { + test('true should be returned in case of git@ dependency', () => { + const packageJson = { + dependencies: { + 'module-name': 'git@github.com:username/repo.git' + } + }; + const response = dependencyAudit.doVersContainGitRepository(packageJson, 'dependencies'); + + expect(response).toBe(true); + }); + + test('true should be returned in case of git:// dependency', () => { + const packageJson = { + dependencies: { + 'module-name': 'git://github.com/username/repo.git' + } + }; + const response = dependencyAudit.doVersContainGitRepository(packageJson, 'dependencies'); + + expect(response).toBe(true); + }); + + test('true should be returned in case of git+https:// dependency', () => { + const packageJson = { + dependencies: { + 'module-name': 'git+https://github.com/username/repo.git' + } + }; + const response = dependencyAudit.doVersContainGitRepository(packageJson, 'dependencies'); + + expect(response).toBe(true); + }); + + test('true should be returned in case of git+ssh:// dependency', () => { + const packageJson = { + dependencies: { + 'module-name': 'git+ssh://github.com/username/repo.git' + } + }; + const response = dependencyAudit.doVersContainGitRepository(packageJson, 'dependencies'); + + expect(response).toBe(true); + }); + + test('true should be returned in case of http:// dependency', () => { + const packageJson = { + dependencies: { + 'module-name': 'http://github.com/username/repo.git' + } + }; + const response = dependencyAudit.doVersContainGitRepository(packageJson, 'dependencies'); + + expect(response).toBe(true); + }); + + test('true should be returned in case of https:// dependency', () => { + const packageJson = { + dependencies: { + 'module-name': 'https://github.com/username/repo.git' + } + }; + const response = dependencyAudit.doVersContainGitRepository(packageJson, 'dependencies'); + + expect(response).toBe(true); + }); + + test('true should be returned in case of github:… dependency', () => { + const packageJson = { + dependencies: { + 'module-name': 'github:username/repo' + } + }; + const response = dependencyAudit.doVersContainGitRepository(packageJson, 'dependencies'); + + expect(response).toBe(true); + }); + + test('true should be returned in case of github shortcut url dependency', () => { + const packageJson = { + dependencies: { + 'module-name': 'username/repo' + } + }; + const response = dependencyAudit.doVersContainGitRepository(packageJson, 'dependencies'); + + expect(response).toBe(true); + }); + + test('true should be returned in case of github shortcut url dependency with branch', () => { + const packageJson = { + dependencies: { + 'module-name': 'org-name/repo#username/issue-42' + } + }; + const response = dependencyAudit.doVersContainGitRepository(packageJson, 'dependencies'); + + expect(response).toBe(true); + }); + + test('true should be returned in case of github shortcut url dependency with tag', () => { + const packageJson = { + dependencies: { + 'module-name': 'username/repo#v2.0.0-rc-1' + } + }; + const response = dependencyAudit.doVersContainGitRepository(packageJson, 'dependencies'); + + expect(response).toBe(true); + }); + }); + }); + describe('doVersContainNonAbsolute method', () => { describe('when the node exists in the package.json file, not all versions are absolute', () => { test('with caret versioning true should be returned', () => { From 6990a98156e0266ed7f89ffd907d4e4680c3819c Mon Sep 17 00:00:00 2001 From: miripiruni Date: Sat, 23 Nov 2019 14:06:19 +0700 Subject: [PATCH 2/4] Add no-archive-dependencies and no-archive-devDependencies rules --- .../dependencies/no-archive-dependencies.md | 81 +++++++++++++++++++ .../no-archive-devDependencies.md | 81 +++++++++++++++++++ src/rules/no-archive-dependencies.js | 20 +++++ src/rules/no-archive-devDependencies.js | 20 +++++ src/validators/dependency-audit.js | 27 ++++++- .../rules/no-archive-dependencies.test.js | 76 +++++++++++++++++ .../rules/no-archive-devDependencies.test.js | 76 +++++++++++++++++ test/unit/validators/dependency-audit.test.js | 60 ++++++++++++++ 8 files changed, 439 insertions(+), 2 deletions(-) create mode 100644 docs/rules/dependencies/no-archive-dependencies.md create mode 100644 docs/rules/dependencies/no-archive-devDependencies.md create mode 100644 src/rules/no-archive-dependencies.js create mode 100644 src/rules/no-archive-devDependencies.js create mode 100644 test/unit/rules/no-archive-dependencies.test.js create mode 100644 test/unit/rules/no-archive-devDependencies.test.js diff --git a/docs/rules/dependencies/no-archive-dependencies.md b/docs/rules/dependencies/no-archive-dependencies.md new file mode 100644 index 00000000..85eb8e47 --- /dev/null +++ b/docs/rules/dependencies/no-archive-dependencies.md @@ -0,0 +1,81 @@ +--- +id: no-archive-dependencies +title: no-archive-dependencies +--- + +Enabling this rule will result in an error being generated if one of the dependencies in `dependencies` is url to archive file. + +## Example .npmpackagejsonlintrc configuration + +```json +{ + "rules": { + "no-archive-dependencies": "error" + } +} +``` + +With exceptions + +```json +{ + "rules": { + "no-archive-dependencies": ["error", { + "exceptions": ["myModule"] + }] + } +} +``` + +## Rule Details + +### *Incorrect* examples + +```json +{ + "dependencies": { + "grunt-npm-package-json-lint": "https://github.com/miripiruni/grunt-npm-package-json-lint/archive/v1.2.3.tar.gz" + } +} +``` + +```json +{ + "dependencies": { + "grunt-npm-package-json-lint": "https://github.com/miripiruni/grunt-npm-package-json-lint/archive/v1.2.3.zip" + } +} +``` + + +### *Correct* examples + +```json +{ + "dependencies": { + "gulp-npm-package-json-lint": "4.0.0" + } +} +``` + +```json +{ + "dependencies": { + "gulp-npm-package-json-lint": "miripiruni/grunt-npm-package-json-lint" + } +} +``` + +## Shorthand for disabling the rule in .npmpackagejsonlintrc configuration + +```json +{ + "rules": { + "no-archive-dependencies": "off" + } +} +``` + +## History + +* Introduced in version 4.3.0 diff --git a/docs/rules/dependencies/no-archive-devDependencies.md b/docs/rules/dependencies/no-archive-devDependencies.md new file mode 100644 index 00000000..a57d4502 --- /dev/null +++ b/docs/rules/dependencies/no-archive-devDependencies.md @@ -0,0 +1,81 @@ +--- +id: no-archive-devDependencies +title: no-archive-devDependencies +--- + +Enabling this rule will result in an error being generated if one of the devDependencies in `devDependencies` is url to archive file. + +## Example .npmpackagejsonlintrc configuration + +```json +{ + "rules": { + "no-archive-devDependencies": "error" + } +} +``` + +With exceptions + +```json +{ + "rules": { + "no-archive-devDependencies": ["error", { + "exceptions": ["myModule"] + }] + } +} +``` + +## Rule Details + +### *Incorrect* examples + +```json +{ + "devDependencies": { + "grunt-npm-package-json-lint": "https://github.com/miripiruni/grunt-npm-package-json-lint/archive/v1.2.3.tar.gz" + } +} +``` + +```json +{ + "devDependencies": { + "grunt-npm-package-json-lint": "https://github.com/miripiruni/grunt-npm-package-json-lint/archive/v1.2.3.zip" + } +} +``` + + +### *Correct* examples + +```json +{ + "devDependencies": { + "gulp-npm-package-json-lint": "4.0.0" + } +} +``` + +```json +{ + "devDependencies": { + "gulp-npm-package-json-lint": "miripiruni/grunt-npm-package-json-lint" + } +} +``` + +## Shorthand for disabling the rule in .npmpackagejsonlintrc configuration + +```json +{ + "rules": { + "no-archive-devDependencies": "off" + } +} +``` + +## History + +* Introduced in version 4.3.0 diff --git a/src/rules/no-archive-dependencies.js b/src/rules/no-archive-dependencies.js new file mode 100644 index 00000000..67318958 --- /dev/null +++ b/src/rules/no-archive-dependencies.js @@ -0,0 +1,20 @@ +const {doVersContainArchiveUrl} = require('./../validators/dependency-audit'); +const LintIssue = require('./../LintIssue'); + +const lintId = 'no-archive-dependencies'; +const nodeName = 'dependencies'; +const message = 'You are using dependencies via url to archive file. Please use dependencies from npm.'; +const ruleType = 'optionalObject'; + +const lint = (packageJsonData, severity, config) => { + if (packageJsonData.hasOwnProperty(nodeName) && doVersContainArchiveUrl(packageJsonData, nodeName, config)) { + return new LintIssue(lintId, severity, nodeName, message); + } + + return true; +}; + +module.exports = { + lint, + ruleType +}; diff --git a/src/rules/no-archive-devDependencies.js b/src/rules/no-archive-devDependencies.js new file mode 100644 index 00000000..4fe08142 --- /dev/null +++ b/src/rules/no-archive-devDependencies.js @@ -0,0 +1,20 @@ +const {doVersContainArchiveUrl} = require('./../validators/dependency-audit'); +const LintIssue = require('./../LintIssue'); + +const lintId = 'no-archive-devDependencies'; +const nodeName = 'devDependencies'; +const message = 'You are using devDependencies via url to archive file. Please use devDependencies from npm.'; +const ruleType = 'optionalObject'; + +const lint = (packageJsonData, severity, config) => { + if (packageJsonData.hasOwnProperty(nodeName) && doVersContainArchiveUrl(packageJsonData, nodeName, config)) { + return new LintIssue(lintId, severity, nodeName, message); + } + + return true; +}; + +module.exports = { + lint, + ruleType +}; diff --git a/src/validators/dependency-audit.js b/src/validators/dependency-audit.js index 3cd32c5e..17ed1f81 100755 --- a/src/validators/dependency-audit.js +++ b/src/validators/dependency-audit.js @@ -289,6 +289,29 @@ const isArchiveUrl = (version) => { return version.endsWith('.tar.gz') || version.endsWith('.zip'); }; +/** + * Determines whether or not dependency versions contains archive url + * @param {object} packageJsonData Valid JSON + * @param {string} nodeName Name of a node in the package.json file + * @param {object} config Rule configuration + * @return {boolean} True if the package contain archive url. + */ +const doVersContainArchiveUrl = (packageJsonData, nodeName, config) => { + for (const dependencyName in packageJsonData[nodeName]) { + if (hasExceptions(config) && config.exceptions.includes(dependencyName)) { + continue; + } + + const dependencyVersion = packageJsonData[nodeName][dependencyName]; + + if (isArchiveUrl(dependencyVersion)) { + return true; + } + } + + return false; +}; + const GITHUB_SHORTCUT_URL = /^(github:)?[^\/]+\/[^\/]+/; /** @@ -300,7 +323,6 @@ const isGithubRepositoryShortcut = (version) => { return GITHUB_SHORTCUT_URL.test(version); }; - module.exports = { hasDependency, hasDepPrereleaseVers, @@ -310,5 +332,6 @@ module.exports = { doVersContainInvalidRange, areVersionsAbsolute, doVersContainNonAbsolute, - doVersContainGitRepository + doVersContainGitRepository, + doVersContainArchiveUrl }; diff --git a/test/unit/rules/no-archive-dependencies.test.js b/test/unit/rules/no-archive-dependencies.test.js new file mode 100644 index 00000000..62d80b8a --- /dev/null +++ b/test/unit/rules/no-archive-dependencies.test.js @@ -0,0 +1,76 @@ +const ruleModule = require('./../../../src/rules/no-archive-dependencies'); + +const {lint, ruleType} = ruleModule; + +describe('no-archive-dependencies Unit Tests', () => { + describe('a rule type value should be exported', () => { + test('it should equal "optionalObject"', () => { + expect(ruleType).toStrictEqual('optionalObject'); + }); + }); + + describe('when package.json has node with an invalid value', () => { + describe('with https://github.com/org/repo/archive/v1.2.3.tar.gz', () => { + test('LintIssue object should be returned', () => { + const packageJsonData = { + dependencies: { + 'test-module': 'https://github.com/org/repo/archive/v1.2.3.tar.gz' + } + }; + const response = lint(packageJsonData, 'error', {exceptions: ['grunt-npm-package-json-lint']}); + + expect(response.lintId).toStrictEqual('no-archive-dependencies'); + expect(response.severity).toStrictEqual('error'); + expect(response.node).toStrictEqual('dependencies'); + expect(response.lintMessage).toStrictEqual( + 'You are using dependencies via url to archive file. Please use dependencies from npm.' + ); + }); + }); + + describe('with https://github.com/org/repo/archive/v1.2.3.zip', () => { + test('LintIssue object should be returned', () => { + const packageJsonData = { + dependencies: { + 'test-module': 'https://github.com/org/repo/archive/v1.2.3.zip' + } + }; + const response = lint(packageJsonData, 'error', {exceptions: ['grunt-npm-package-json-lint']}); + + expect(response.lintId).toStrictEqual('no-archive-dependencies'); + expect(response.severity).toStrictEqual('error'); + expect(response.node).toStrictEqual('dependencies'); + expect(response.lintMessage).toStrictEqual( + 'You are using dependencies via url to archive file. Please use dependencies from npm.' + ); + }); + }); + }); + + describe('when package.json has node with a invalid value and config exception', () => { + test('true should be returned', () => { + const packageJsonData = { + dependencies: { + 'my-module': 'https://github.com/org/repo/archive/v1.2.3.zip' + } + }; + const response = lint(packageJsonData, 'error', {exceptions: ['my-module']}); + + expect(response).toBe(true); + }); + }); + + describe('when package.json has node with a valid value', () => { + test('true should be returned', () => { + const packageJsonData = { + dependencies: { + 'my-module': 'username/repo', + 'my-other-module': '1.2.3' + } + }; + const response = lint(packageJsonData, 'error'); + + expect(response).toBe(true); + }); + }); +}); diff --git a/test/unit/rules/no-archive-devDependencies.test.js b/test/unit/rules/no-archive-devDependencies.test.js new file mode 100644 index 00000000..2f31e39f --- /dev/null +++ b/test/unit/rules/no-archive-devDependencies.test.js @@ -0,0 +1,76 @@ +const ruleModule = require('./../../../src/rules/no-archive-devDependencies'); + +const {lint, ruleType} = ruleModule; + +describe('no-archive-devDependencies Unit Tests', () => { + describe('a rule type value should be exported', () => { + test('it should equal "optionalObject"', () => { + expect(ruleType).toStrictEqual('optionalObject'); + }); + }); + + describe('when package.json has node with an invalid value', () => { + describe('with https://github.com/org/repo/archive/v1.2.3.tar.gz', () => { + test('LintIssue object should be returned', () => { + const packageJsonData = { + devDependencies: { + 'test-module': 'https://github.com/org/repo/archive/v1.2.3.tar.gz' + } + }; + const response = lint(packageJsonData, 'error', {exceptions: ['grunt-npm-package-json-lint']}); + + expect(response.lintId).toStrictEqual('no-archive-devDependencies'); + expect(response.severity).toStrictEqual('error'); + expect(response.node).toStrictEqual('devDependencies'); + expect(response.lintMessage).toStrictEqual( + 'You are using devDependencies via url to archive file. Please use devDependencies from npm.' + ); + }); + }); + + describe('with https://github.com/org/repo/archive/v1.2.3.zip', () => { + test('LintIssue object should be returned', () => { + const packageJsonData = { + devDependencies: { + 'test-module': 'https://github.com/org/repo/archive/v1.2.3.zip' + } + }; + const response = lint(packageJsonData, 'error', {exceptions: ['grunt-npm-package-json-lint']}); + + expect(response.lintId).toStrictEqual('no-archive-devDependencies'); + expect(response.severity).toStrictEqual('error'); + expect(response.node).toStrictEqual('devDependencies'); + expect(response.lintMessage).toStrictEqual( + 'You are using devDependencies via url to archive file. Please use devDependencies from npm.' + ); + }); + }); + }); + + describe('when package.json has node with a invalid value and config exception', () => { + test('true should be returned', () => { + const packageJsonData = { + devDependencies: { + 'my-module': 'https://github.com/org/repo/archive/v1.2.3.zip' + } + }; + const response = lint(packageJsonData, 'error', {exceptions: ['my-module']}); + + expect(response).toBe(true); + }); + }); + + describe('when package.json has node with a valid value', () => { + test('true should be returned', () => { + const packageJsonData = { + devDependencies: { + 'my-module': 'username/repo', + 'my-other-module': '1.2.3' + } + }; + const response = lint(packageJsonData, 'error'); + + expect(response).toBe(true); + }); + }); +}); diff --git a/test/unit/validators/dependency-audit.test.js b/test/unit/validators/dependency-audit.test.js index cae31642..48f9d254 100755 --- a/test/unit/validators/dependency-audit.test.js +++ b/test/unit/validators/dependency-audit.test.js @@ -964,4 +964,64 @@ describe('dependency-audit Unit Tests', () => { }); }); }); + + describe('doVersContainArchiveUrl method', () => { + describe('when the node exists in the package.json file, some versions are archive url', () => { + test('with github dependency true should be returned', () => { + const packageJson = { + dependencies: { + 'my-module': 'https://github.com/miripiruni/repo/archive/v1.2.3.tar.gz' + } + }; + const response = dependencyAudit.doVersContainArchiveUrl(packageJson, 'dependencies'); + + expect(response).toBe(true); + }); + + test('with github dependency true should be returned', () => { + const packageJson = { + dependencies: { + 'my-module': 'https://github.com/miripiruni/repo/archive/v1.2.3.zip' + } + }; + const response = dependencyAudit.doVersContainArchiveUrl(packageJson, 'dependencies'); + + expect(response).toBe(true); + }); + }); + + describe('when the node exists in the package.json file, all versions are non git', () => { + test('false should be returned', () => { + const packageJson = { + dependencies: { + 'npm-package-json-lint': '1.0.0', + 'grunt-npm-package-json-lint': '2.1.0', + 'gulp-npm-package-json-lint': '=2.4.0', + 'module-from-local': 'file:local-module', + 'module-from-archive': 'https://github.com/user/repo.git' + } + }; + const response = dependencyAudit.doVersContainArchiveUrl(packageJson, 'dependencies'); + + expect(response).toBe(false); + }); + }); + + describe('when the node exists in the package.json file, one absolute version but has expection', () => { + test('false should be returned', () => { + const packageJson = { + dependencies: { + 'module-from-archive': 'https://github.com/miripiruni/repo/archive/v1.2.3.zip', + 'grunt-npm-package-json-lint': '2.0.0', + 'gulp-npm-package-json-lint': '^2.0.0' + } + }; + const response = dependencyAudit.doVersContainGitRepository(packageJson, 'dependencies', { + exceptions: ['module-from-archive'] + }); + + expect(response).toBe(false); + }); + }); + }); }); From 1b880e2174d844ec734506fe622c400a327ae9d8 Mon Sep 17 00:00:00 2001 From: miripiruni Date: Sat, 23 Nov 2019 14:59:40 +0700 Subject: [PATCH 3/4] Add no-file-dependencies and no-file-devDependencies rules --- .../dependencies/no-file-dependencies.md | 66 +++++++++++++++++++ .../dependencies/no-file-devDependencies.md | 66 +++++++++++++++++++ src/rules/no-file-dependencies.js | 20 ++++++ src/rules/no-file-devDependencies.js | 20 ++++++ src/validators/dependency-audit.js | 26 +++++++- test/unit/rules/no-file-dependencies.test.js | 56 ++++++++++++++++ .../rules/no-file-devDependencies.test.js | 56 ++++++++++++++++ test/unit/validators/dependency-audit.test.js | 49 ++++++++++++++ 8 files changed, 358 insertions(+), 1 deletion(-) create mode 100644 docs/rules/dependencies/no-file-dependencies.md create mode 100644 docs/rules/dependencies/no-file-devDependencies.md create mode 100644 src/rules/no-file-dependencies.js create mode 100644 src/rules/no-file-devDependencies.js create mode 100644 test/unit/rules/no-file-dependencies.test.js create mode 100644 test/unit/rules/no-file-devDependencies.test.js diff --git a/docs/rules/dependencies/no-file-dependencies.md b/docs/rules/dependencies/no-file-dependencies.md new file mode 100644 index 00000000..76afba43 --- /dev/null +++ b/docs/rules/dependencies/no-file-dependencies.md @@ -0,0 +1,66 @@ +--- +id: no-file-dependencies +title: no-file-dependencies +--- + +Enabling this rule will result in an error being generated if one of the dependencies in `dependencies` is url to local file. + +## Example .npmpackagejsonlintrc configuration + +```json +{ + "rules": { + "no-file-dependencies": "error" + } +} +``` + +With exceptions + +```json +{ + "rules": { + "no-file-dependencies": ["error", { + "exceptions": ["myModule"] + }] + } +} +``` + +## Rule Details + +### *Incorrect* examples + +```json +{ + "dependencies": { + "my-module": "file:local-module" + } +} +``` + + +### *Correct* examples + + +```json +{ + "dependencies": { + "gulp-npm-package-json-lint": "miripiruni/grunt-npm-package-json-lint" + } +} +``` + +## Shorthand for disabling the rule in .npmpackagejsonlintrc configuration + +```json +{ + "rules": { + "no-file-dependencies": "off" + } +} +``` + +## History + +* Introduced in version 4.3.0 diff --git a/docs/rules/dependencies/no-file-devDependencies.md b/docs/rules/dependencies/no-file-devDependencies.md new file mode 100644 index 00000000..02979192 --- /dev/null +++ b/docs/rules/dependencies/no-file-devDependencies.md @@ -0,0 +1,66 @@ +--- +id: no-file-devDependencies +title: no-file-devDependencies +--- + +Enabling this rule will result in an error being generated if one of the devDependencies in `devDependencies` is url to local file. + +## Example .npmpackagejsonlintrc configuration + +```json +{ + "rules": { + "no-file-devDependencies": "error" + } +} +``` + +With exceptions + +```json +{ + "rules": { + "no-file-devDependencies": ["error", { + "exceptions": ["myModule"] + }] + } +} +``` + +## Rule Details + +### *Incorrect* examples + +```json +{ + "devDependencies": { + "my-module": "file:local-module" + } +} +``` + + +### *Correct* examples + + +```json +{ + "devDependencies": { + "gulp-npm-package-json-lint": "miripiruni/grunt-npm-package-json-lint" + } +} +``` + +## Shorthand for disabling the rule in .npmpackagejsonlintrc configuration + +```json +{ + "rules": { + "no-file-devDependencies": "off" + } +} +``` + +## History + +* Introduced in version 4.3.0 diff --git a/src/rules/no-file-dependencies.js b/src/rules/no-file-dependencies.js new file mode 100644 index 00000000..8eebd1c1 --- /dev/null +++ b/src/rules/no-file-dependencies.js @@ -0,0 +1,20 @@ +const {doVersContainFileUrl} = require('./../validators/dependency-audit'); +const LintIssue = require('./../LintIssue'); + +const lintId = 'no-file-dependencies'; +const nodeName = 'dependencies'; +const message = 'You are using dependencies via url to local file. Please use dependencies from npm.'; +const ruleType = 'optionalObject'; + +const lint = (packageJsonData, severity, config) => { + if (packageJsonData.hasOwnProperty(nodeName) && doVersContainFileUrl(packageJsonData, nodeName, config)) { + return new LintIssue(lintId, severity, nodeName, message); + } + + return true; +}; + +module.exports = { + lint, + ruleType +}; diff --git a/src/rules/no-file-devDependencies.js b/src/rules/no-file-devDependencies.js new file mode 100644 index 00000000..1a68a3ab --- /dev/null +++ b/src/rules/no-file-devDependencies.js @@ -0,0 +1,20 @@ +const {doVersContainFileUrl} = require('./../validators/dependency-audit'); +const LintIssue = require('./../LintIssue'); + +const lintId = 'no-file-devDependencies'; +const nodeName = 'devDependencies'; +const message = 'You are using devDependencies via url to local file. Please use devDependencies from npm.'; +const ruleType = 'optionalObject'; + +const lint = (packageJsonData, severity, config) => { + if (packageJsonData.hasOwnProperty(nodeName) && doVersContainFileUrl(packageJsonData, nodeName, config)) { + return new LintIssue(lintId, severity, nodeName, message); + } + + return true; +}; + +module.exports = { + lint, + ruleType +}; diff --git a/src/validators/dependency-audit.js b/src/validators/dependency-audit.js index 17ed1f81..7dd2a7a9 100755 --- a/src/validators/dependency-audit.js +++ b/src/validators/dependency-audit.js @@ -323,6 +323,29 @@ const isGithubRepositoryShortcut = (version) => { return GITHUB_SHORTCUT_URL.test(version); }; +/** + * Determines whether or not dependency versions contains file url + * @param {object} packageJsonData Valid JSON + * @param {string} nodeName Name of a node in the package.json file + * @param {object} config Rule configuration + * @return {boolean} True if the package contain file url. + */ +const doVersContainFileUrl = (packageJsonData, nodeName, config) => { + for (const dependencyName in packageJsonData[nodeName]) { + if (hasExceptions(config) && config.exceptions.includes(dependencyName)) { + continue; + } + + const dependencyVersion = packageJsonData[nodeName][dependencyName]; + + if (dependencyVersion.startsWith('file:')) { + return true; + } + } + + return false; +}; + module.exports = { hasDependency, hasDepPrereleaseVers, @@ -333,5 +356,6 @@ module.exports = { areVersionsAbsolute, doVersContainNonAbsolute, doVersContainGitRepository, - doVersContainArchiveUrl + doVersContainArchiveUrl, + doVersContainFileUrl }; diff --git a/test/unit/rules/no-file-dependencies.test.js b/test/unit/rules/no-file-dependencies.test.js new file mode 100644 index 00000000..cad6af1f --- /dev/null +++ b/test/unit/rules/no-file-dependencies.test.js @@ -0,0 +1,56 @@ +const ruleModule = require('./../../../src/rules/no-file-dependencies'); + +const {lint, ruleType} = ruleModule; + +describe('no-archive-dependencies Unit Tests', () => { + describe('a rule type value should be exported', () => { + test('it should equal "optionalObject"', () => { + expect(ruleType).toStrictEqual('optionalObject'); + }); + }); + + describe('when package.json has node with an invalid value', () => { + test('LintIssue object should be returned', () => { + const packageJsonData = { + dependencies: { + 'test-module': 'file:local-directory' + } + }; + const response = lint(packageJsonData, 'error', {exceptions: ['grunt-npm-package-json-lint']}); + + expect(response.lintId).toStrictEqual('no-file-dependencies'); + expect(response.severity).toStrictEqual('error'); + expect(response.node).toStrictEqual('dependencies'); + expect(response.lintMessage).toStrictEqual( + 'You are using dependencies via url to local file. Please use dependencies from npm.' + ); + }); + }); + + describe('when package.json has node with a invalid value and config exception', () => { + test('true should be returned', () => { + const packageJsonData = { + dependencies: { + 'my-module': 'file:local-directory' + } + }; + const response = lint(packageJsonData, 'error', {exceptions: ['my-module']}); + + expect(response).toBe(true); + }); + }); + + describe('when package.json has node with a valid value', () => { + test('true should be returned', () => { + const packageJsonData = { + dependencies: { + 'my-module': 'username/repo', + 'my-other-module': '1.2.3' + } + }; + const response = lint(packageJsonData, 'error'); + + expect(response).toBe(true); + }); + }); +}); diff --git a/test/unit/rules/no-file-devDependencies.test.js b/test/unit/rules/no-file-devDependencies.test.js new file mode 100644 index 00000000..7e728114 --- /dev/null +++ b/test/unit/rules/no-file-devDependencies.test.js @@ -0,0 +1,56 @@ +const ruleModule = require('./../../../src/rules/no-file-devDependencies'); + +const {lint, ruleType} = ruleModule; + +describe('no-archive-devDependencies Unit Tests', () => { + describe('a rule type value should be exported', () => { + test('it should equal "optionalObject"', () => { + expect(ruleType).toStrictEqual('optionalObject'); + }); + }); + + describe('when package.json has node with an invalid value', () => { + test('LintIssue object should be returned', () => { + const packageJsonData = { + devDependencies: { + 'test-module': 'file:local-directory' + } + }; + const response = lint(packageJsonData, 'error', {exceptions: ['grunt-npm-package-json-lint']}); + + expect(response.lintId).toStrictEqual('no-file-devDependencies'); + expect(response.severity).toStrictEqual('error'); + expect(response.node).toStrictEqual('devDependencies'); + expect(response.lintMessage).toStrictEqual( + 'You are using devDependencies via url to local file. Please use devDependencies from npm.' + ); + }); + }); + + describe('when package.json has node with a invalid value and config exception', () => { + test('true should be returned', () => { + const packageJsonData = { + devDependencies: { + 'my-module': 'file:local-directory' + } + }; + const response = lint(packageJsonData, 'error', {exceptions: ['my-module']}); + + expect(response).toBe(true); + }); + }); + + describe('when package.json has node with a valid value', () => { + test('true should be returned', () => { + const packageJsonData = { + devDependencies: { + 'my-module': 'username/repo', + 'my-other-module': '1.2.3' + } + }; + const response = lint(packageJsonData, 'error'); + + expect(response).toBe(true); + }); + }); +}); diff --git a/test/unit/validators/dependency-audit.test.js b/test/unit/validators/dependency-audit.test.js index 48f9d254..0306f04d 100755 --- a/test/unit/validators/dependency-audit.test.js +++ b/test/unit/validators/dependency-audit.test.js @@ -1024,4 +1024,53 @@ describe('dependency-audit Unit Tests', () => { }); }); }); + + describe('doVersContainFileUrl method', () => { + describe('when the node exists in the package.json file, some versions are url to file', () => { + test('with github dependency true should be returned', () => { + const packageJson = { + dependencies: { + 'my-module': 'file:local-module' + } + }; + const response = dependencyAudit.doVersContainFileUrl(packageJson, 'dependencies'); + + expect(response).toBe(true); + }); + }); + + describe('when the node exists in the package.json file, all versions are non file url', () => { + test('false should be returned', () => { + const packageJson = { + dependencies: { + 'npm-package-json-lint': '1.0.0', + 'grunt-npm-package-json-lint': '2.1.0', + 'gulp-npm-package-json-lint': '=2.4.0', + 'module-from-git': 'https://github.com/user/repo.git', + 'module-from-archive': 'https://github.com/user/repo/archive/v1.2.3.tar.gz' + } + }; + const response = dependencyAudit.doVersContainFileUrl(packageJson, 'dependencies'); + + expect(response).toBe(false); + }); + }); + + describe('when the node exists in the package.json file, one absolute version but has expection', () => { + test('false should be returned', () => { + const packageJson = { + dependencies: { + 'module-from-file': 'file:local-module', + 'grunt-npm-package-json-lint': '2.0.0', + 'gulp-npm-package-json-lint': '^2.0.0' + } + }; + const response = dependencyAudit.doVersContainFileUrl(packageJson, 'dependencies', { + exceptions: ['module-from-file'] + }); + + expect(response).toBe(false); + }); + }); + }); }); From 02828778e53c06ac30daa6c75e647131cd3834e9 Mon Sep 17 00:00:00 2001 From: miripiruni Date: Sat, 23 Nov 2019 15:35:38 +0700 Subject: [PATCH 4/4] Fix linter issues --- src/validators/dependency-audit.js | 81 +++++++++---------- test/unit/rules/no-git-dependencies.test.js | 22 ++--- .../unit/rules/no-git-devDependencies.test.js | 22 ++--- test/unit/validators/dependency-audit.test.js | 4 +- 4 files changed, 61 insertions(+), 68 deletions(-) diff --git a/src/validators/dependency-audit.js b/src/validators/dependency-audit.js index 7dd2a7a9..ea631068 100755 --- a/src/validators/dependency-audit.js +++ b/src/validators/dependency-audit.js @@ -225,27 +225,24 @@ const doVersContainNonAbsolute = (packageJsonData, nodeName, config) => { return dependenciesChecked > 0 ? !onlyAbsoluteVersionDetected : false; }; +const GITHUB_SHORTCUT_URL = /^(github:)?[^/]+\/[^/]+/; + /** - * Determines whether or not dependency versions are git repository - * @param {object} packageJsonData Valid JSON - * @param {string} nodeName Name of a node in the package.json file - * @param {object} config Rule configuration - * @return {boolean} True if the package has an git repo. + * Determines whether or not version is a shortcut to github repository + * @param version value of package's version + * @return {boolean} True if the version is a shortcut to github repository */ -const doVersContainGitRepository = (packageJsonData, nodeName, config) => { - for (const dependencyName in packageJsonData[nodeName]) { - if (hasExceptions(config) && config.exceptions.includes(dependencyName)) { - continue; - } - - const dependencyVersion = packageJsonData[nodeName][dependencyName]; - - if (isGitRepositoryUrl(dependencyVersion) || isGithubRepositoryShortcut(dependencyVersion)) { - return true; - } - } +const isGithubRepositoryShortcut = version => { + return GITHUB_SHORTCUT_URL.test(version); +}; - return false; +/** + * Determines whether or not version is url to archive + * @param version value of package's version + * @return {boolean} True if the version is url to archive + */ +const isArchiveUrl = version => { + return version.endsWith('.tar.gz') || version.endsWith('.zip'); }; /** @@ -253,24 +250,17 @@ const doVersContainGitRepository = (packageJsonData, nodeName, config) => { * @param version value of package's version * @return {boolean} True if the version is an git repo url. */ -const isGitRepositoryUrl = (version) => { +const isGitRepositoryUrl = version => { if (isArchiveUrl(version)) { return false; } // based on https://github.com/npm/hosted-git-info - const protocols = new Set([ - 'git@', - 'git://', - 'git+https://', - 'git+ssh://', - 'http://', - 'https://' - ]); + const protocols = new Set(['git@', 'git://', 'git+https://', 'git+ssh://', 'http://', 'https://']); let match = false; - for (let protocol of protocols) { + for (const protocol of protocols) { if (version.startsWith(protocol)) { match = true; break; @@ -281,12 +271,26 @@ const isGitRepositoryUrl = (version) => { }; /** - * Determines whether or not version is url to archive - * @param version value of package's version - * @return {boolean} True if the version is url to archive + * Determines whether or not dependency versions are git repository + * @param {object} packageJsonData Valid JSON + * @param {string} nodeName Name of a node in the package.json file + * @param {object} config Rule configuration + * @return {boolean} True if the package has an git repo. */ -const isArchiveUrl = (version) => { - return version.endsWith('.tar.gz') || version.endsWith('.zip'); +const doVersContainGitRepository = (packageJsonData, nodeName, config) => { + for (const dependencyName in packageJsonData[nodeName]) { + if (hasExceptions(config) && config.exceptions.includes(dependencyName)) { + continue; + } + + const dependencyVersion = packageJsonData[nodeName][dependencyName]; + + if (isGitRepositoryUrl(dependencyVersion) || isGithubRepositoryShortcut(dependencyVersion)) { + return true; + } + } + + return false; }; /** @@ -312,17 +316,6 @@ const doVersContainArchiveUrl = (packageJsonData, nodeName, config) => { return false; }; -const GITHUB_SHORTCUT_URL = /^(github:)?[^\/]+\/[^\/]+/; - -/** - * Determines whether or not version is a shortcut to github repository - * @param version value of package's version - * @return {boolean} True if the version is a shortcut to github repository - */ -const isGithubRepositoryShortcut = (version) => { - return GITHUB_SHORTCUT_URL.test(version); -}; - /** * Determines whether or not dependency versions contains file url * @param {object} packageJsonData Valid JSON diff --git a/test/unit/rules/no-git-dependencies.test.js b/test/unit/rules/no-git-dependencies.test.js index c2ac3f60..e2983124 100644 --- a/test/unit/rules/no-git-dependencies.test.js +++ b/test/unit/rules/no-git-dependencies.test.js @@ -14,7 +14,7 @@ describe('no-git-dependencies Unit Tests', () => { test('LintIssue object should be returned', () => { const packageJsonData = { dependencies: { - 'test': 'github:username/repo' + 'my-module': 'github:username/repo' } }; const response = lint(packageJsonData, 'error', {exceptions: ['grunt-npm-package-json-lint']}); @@ -32,7 +32,7 @@ describe('no-git-dependencies Unit Tests', () => { test('LintIssue object should be returned', () => { const packageJsonData = { dependencies: { - 'test': 'username/repo' + 'my-module': 'username/repo' } }; const response = lint(packageJsonData, 'error', {exceptions: ['grunt-npm-package-json-lint']}); @@ -50,7 +50,7 @@ describe('no-git-dependencies Unit Tests', () => { test('LintIssue object should be returned', () => { const packageJsonData = { dependencies: { - 'test': 'username/repo#author/issue' + 'my-module': 'username/repo#author/issue' } }; const response = lint(packageJsonData, 'error', {exceptions: ['grunt-npm-package-json-lint']}); @@ -68,7 +68,7 @@ describe('no-git-dependencies Unit Tests', () => { test('LintIssue object should be returned', () => { const packageJsonData = { dependencies: { - 'test': 'username/repo#v1.0.0-rc-1' + 'my-module': 'username/repo#v1.0.0-rc-1' } }; const response = lint(packageJsonData, 'error', {exceptions: ['grunt-npm-package-json-lint']}); @@ -86,7 +86,7 @@ describe('no-git-dependencies Unit Tests', () => { test('LintIssue object should be returned', () => { const packageJsonData = { dependencies: { - 'test': 'username/repo#4f9012b132aa4d2d6097b516b31327c999b0a846' + 'my-module': 'username/repo#4f9012b132aa4d2d6097b516b31327c999b0a846' } }; const response = lint(packageJsonData, 'error', {exceptions: ['grunt-npm-package-json-lint']}); @@ -104,7 +104,7 @@ describe('no-git-dependencies Unit Tests', () => { test('LintIssue object should be returned', () => { const packageJsonData = { dependencies: { - 'test': 'git://github.com/user/repo.git' + 'my-module': 'git://github.com/user/repo.git' } }; const response = lint(packageJsonData, 'error', {exceptions: ['grunt-npm-package-json-lint']}); @@ -122,7 +122,7 @@ describe('no-git-dependencies Unit Tests', () => { test('LintIssue object should be returned', () => { const packageJsonData = { dependencies: { - 'test': 'git@github.com:user/repo.git' + 'my-module': 'git@github.com:user/repo.git' } }; const response = lint(packageJsonData, 'error', {exceptions: ['grunt-npm-package-json-lint']}); @@ -140,7 +140,7 @@ describe('no-git-dependencies Unit Tests', () => { test('LintIssue object should be returned', () => { const packageJsonData = { dependencies: { - 'test': 'git+https://github.com/user/repo.git' + 'my-module': 'git+https://github.com/user/repo.git' } }; const response = lint(packageJsonData, 'error', {exceptions: ['grunt-npm-package-json-lint']}); @@ -158,7 +158,7 @@ describe('no-git-dependencies Unit Tests', () => { test('LintIssue object should be returned', () => { const packageJsonData = { dependencies: { - 'test': 'git+ssh://github.com/user/repo.git' + 'my-module': 'git+ssh://github.com/user/repo.git' } }; const response = lint(packageJsonData, 'error', {exceptions: ['grunt-npm-package-json-lint']}); @@ -176,7 +176,7 @@ describe('no-git-dependencies Unit Tests', () => { test('LintIssue object should be returned', () => { const packageJsonData = { dependencies: { - 'test': 'http://github.com/user/repo.git' + 'my-module': 'http://github.com/user/repo.git' } }; const response = lint(packageJsonData, 'error', {exceptions: ['grunt-npm-package-json-lint']}); @@ -194,7 +194,7 @@ describe('no-git-dependencies Unit Tests', () => { test('LintIssue object should be returned', () => { const packageJsonData = { dependencies: { - 'test': 'https://github.com/user/repo.git' + 'my-module': 'https://github.com/user/repo.git' } }; const response = lint(packageJsonData, 'error', {exceptions: ['grunt-npm-package-json-lint']}); diff --git a/test/unit/rules/no-git-devDependencies.test.js b/test/unit/rules/no-git-devDependencies.test.js index 1caea3b4..38cc03e8 100644 --- a/test/unit/rules/no-git-devDependencies.test.js +++ b/test/unit/rules/no-git-devDependencies.test.js @@ -14,7 +14,7 @@ describe('no-git-devDependencies Unit Tests', () => { test('LintIssue object should be returned', () => { const packageJsonData = { devDependencies: { - 'test': 'github:username/repo' + 'my-module': 'github:username/repo' } }; const response = lint(packageJsonData, 'error', {exceptions: ['grunt-npm-package-json-lint']}); @@ -32,7 +32,7 @@ describe('no-git-devDependencies Unit Tests', () => { test('LintIssue object should be returned', () => { const packageJsonData = { devDependencies: { - 'test': 'username/repo' + 'my-module': 'username/repo' } }; const response = lint(packageJsonData, 'error', {exceptions: ['grunt-npm-package-json-lint']}); @@ -50,7 +50,7 @@ describe('no-git-devDependencies Unit Tests', () => { test('LintIssue object should be returned', () => { const packageJsonData = { devDependencies: { - 'test': 'username/repo#author/issue' + 'my-module': 'username/repo#author/issue' } }; const response = lint(packageJsonData, 'error', {exceptions: ['grunt-npm-package-json-lint']}); @@ -68,7 +68,7 @@ describe('no-git-devDependencies Unit Tests', () => { test('LintIssue object should be returned', () => { const packageJsonData = { devDependencies: { - 'test': 'username/repo#v1.0.0-rc-1' + 'my-module': 'username/repo#v1.0.0-rc-1' } }; const response = lint(packageJsonData, 'error', {exceptions: ['grunt-npm-package-json-lint']}); @@ -86,7 +86,7 @@ describe('no-git-devDependencies Unit Tests', () => { test('LintIssue object should be returned', () => { const packageJsonData = { devDependencies: { - 'test': 'username/repo#4f9012b132aa4d2d6097b516b31327c999b0a846' + 'my-module': 'username/repo#4f9012b132aa4d2d6097b516b31327c999b0a846' } }; const response = lint(packageJsonData, 'error', {exceptions: ['grunt-npm-package-json-lint']}); @@ -104,7 +104,7 @@ describe('no-git-devDependencies Unit Tests', () => { test('LintIssue object should be returned', () => { const packageJsonData = { devDependencies: { - 'test': 'git://github.com/user/repo.git' + 'my-module': 'git://github.com/user/repo.git' } }; const response = lint(packageJsonData, 'error', {exceptions: ['grunt-npm-package-json-lint']}); @@ -122,7 +122,7 @@ describe('no-git-devDependencies Unit Tests', () => { test('LintIssue object should be returned', () => { const packageJsonData = { devDependencies: { - 'test': 'git@github.com:user/repo.git' + 'my-module': 'git@github.com:user/repo.git' } }; const response = lint(packageJsonData, 'error', {exceptions: ['grunt-npm-package-json-lint']}); @@ -140,7 +140,7 @@ describe('no-git-devDependencies Unit Tests', () => { test('LintIssue object should be returned', () => { const packageJsonData = { devDependencies: { - 'test': 'git+https://github.com/user/repo.git' + 'my-module': 'git+https://github.com/user/repo.git' } }; const response = lint(packageJsonData, 'error', {exceptions: ['grunt-npm-package-json-lint']}); @@ -158,7 +158,7 @@ describe('no-git-devDependencies Unit Tests', () => { test('LintIssue object should be returned', () => { const packageJsonData = { devDependencies: { - 'test': 'git+ssh://github.com/user/repo.git' + 'my-module': 'git+ssh://github.com/user/repo.git' } }; const response = lint(packageJsonData, 'error', {exceptions: ['grunt-npm-package-json-lint']}); @@ -176,7 +176,7 @@ describe('no-git-devDependencies Unit Tests', () => { test('LintIssue object should be returned', () => { const packageJsonData = { devDependencies: { - 'test': 'http://github.com/user/repo.git' + 'my-module': 'http://github.com/user/repo.git' } }; const response = lint(packageJsonData, 'error', {exceptions: ['grunt-npm-package-json-lint']}); @@ -194,7 +194,7 @@ describe('no-git-devDependencies Unit Tests', () => { test('LintIssue object should be returned', () => { const packageJsonData = { devDependencies: { - 'test': 'https://github.com/user/repo.git' + 'my-module': 'https://github.com/user/repo.git' } }; const response = lint(packageJsonData, 'error', {exceptions: ['grunt-npm-package-json-lint']}); diff --git a/test/unit/validators/dependency-audit.test.js b/test/unit/validators/dependency-audit.test.js index 0306f04d..5f6b95e3 100755 --- a/test/unit/validators/dependency-audit.test.js +++ b/test/unit/validators/dependency-audit.test.js @@ -967,7 +967,7 @@ describe('dependency-audit Unit Tests', () => { describe('doVersContainArchiveUrl method', () => { describe('when the node exists in the package.json file, some versions are archive url', () => { - test('with github dependency true should be returned', () => { + test('with tar.gz dependency true should be returned', () => { const packageJson = { dependencies: { 'my-module': 'https://github.com/miripiruni/repo/archive/v1.2.3.tar.gz' @@ -978,7 +978,7 @@ describe('dependency-audit Unit Tests', () => { expect(response).toBe(true); }); - test('with github dependency true should be returned', () => { + test('with zip dependency true should be returned', () => { const packageJson = { dependencies: { 'my-module': 'https://github.com/miripiruni/repo/archive/v1.2.3.zip'