diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 00000000..bd70ba34 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,5 @@ +{ + "yaml.schemas": { + "https://raw.githubusercontent.com/newrelic-forks/repolinter/master/rulesets/schema.json": ["/repolinter.yml", "/repolinter.yaml"] + } +} diff --git a/axioms/licensee.js b/axioms/licensee.js index 473fabb5..96764d19 100644 --- a/axioms/licensee.js +++ b/axioms/licensee.js @@ -4,16 +4,12 @@ const licensee = require('../lib/licensee') const Result = require('../lib/result') -module.exports = function (fileSystem) { +module.exports = async function (fileSystem) { let licenses = [] try { - licenses = licensee.identifyLicensesSync(fileSystem.targetDir) + licenses = await licensee.identifyLicense(fileSystem.targetDir) } catch (error) { - if (error.message === 'Licensee not installed') { - return new Result('Licensee not found in path, only running license-independent rules', [], false) - } else { - return new Result(error.message, [], false) - } + return new Result(error.message, [], false) } return new Result('', licenses.map(l => { return { passed: true, path: l } }), true) } diff --git a/axioms/linguist.js b/axioms/linguist.js index b977aab8..f77695a7 100644 --- a/axioms/linguist.js +++ b/axioms/linguist.js @@ -4,19 +4,15 @@ const linguist = require('../lib/linguist') const Result = require('../lib/result') -module.exports = function (fileSystem) { +module.exports = async function (fileSystem) { const languages = [] try { - var jsonObj = linguist.identifyLanguagesSync(fileSystem.targetDir) + var jsonObj = await linguist.identifyLanguages(fileSystem.targetDir) for (var language in jsonObj) { languages.push(language.toLowerCase()) } } catch (error) { - if (error.message === 'Linguist not installed') { - return new Result('Linguist not found in path, only running language-independent rules', [], false) - } else { - return new Result(error.message, [], false) - } + return new Result(error.message, [], false) } return new Result('', languages.map(l => { return { passed: true, path: l } }), true) } diff --git a/formatters/markdown_formatter.js b/formatters/markdown_formatter.js index 6df456e6..f4b00361 100644 --- a/formatters/markdown_formatter.js +++ b/formatters/markdown_formatter.js @@ -111,26 +111,29 @@ class MarkdownFormatter { const start = '\n\n' + opWrap(null, result.ruleInfo.policyInfo, '. ') + opWrap('For more information please visit ', result.ruleInfo.policyUrl, '. ') + - opWrap(null, result.lintResult.message, '. ') + - 'Below is a list of files or patterns that failed:\n\n' + opWrap(null, result.lintResult.message, '. ') formatBase.push(start) - // create bulleted list - // format the result based on these pieces of information - const list = result.lintResult.targets - // filter only failed targets - .filter(t => t.passed === false) - // match each target to it's fix result, if one exists - .map(t => - result.fixResult && t.path ? [t, result.fixResult.targets.find(f => f.path === t.path) || null] : [t, null]) - .map(([lintTarget, fixTarget]) => { - const base = `- \`${lintTarget.path || lintTarget.pattern}\`${opWrap(': ', lintTarget.message, '.')}` - // no fix format - if (!fixTarget || !fixTarget.passed) { return base } - // with fix format - return base + `\n - ${dryRun ? SUGGESTED_FIX : APPLIED_FIX} ${fixTarget.message || result.fixResult.message}` - }) - .join('\n') - formatBase.push(list) + // create bulleted list, filter only failed targets + const failedList = result.lintResult.targets.filter(t => t.passed === false) + if (failedList.length === 0) { + formatBase.push('All files passed this test.') + } else { + formatBase.push('Below is a list of files or patterns that failed:\n\n') + // format the result based on these pieces of information + const list = failedList + // match each target to it's fix result, if one exists + .map(t => + result.fixResult && t.path ? [t, result.fixResult.targets.find(f => f.path === t.path) || null] : [t, null]) + .map(([lintTarget, fixTarget]) => { + const base = `- \`${lintTarget.path || lintTarget.pattern}\`${opWrap(': ', lintTarget.message, '.')}` + // no fix format + if (!fixTarget || !fixTarget.passed) { return base } + // with fix format + return base + `\n - ${dryRun ? SUGGESTED_FIX : APPLIED_FIX} ${fixTarget.message || result.fixResult.message}` + }) + .join('\n') + formatBase.push(list) + } } // suggested fix for overall rule/fix combo if (result.fixResult && result.fixResult.passed) { diff --git a/index.js b/index.js index c10a147e..b8aa9c0f 100644 --- a/index.js +++ b/index.js @@ -152,7 +152,7 @@ async function lint (targetDir, filterPaths = [], ruleset = null, dryRun = false errMsg: e && e.toString(), results: [], targets: {}, - formatOptions: ruleset.formatOptions + formatOptions: ruleset && ruleset.formatOptions } } } diff --git a/lib/command_exists.js b/lib/command_exists.js new file mode 100644 index 00000000..fb8679a3 --- /dev/null +++ b/lib/command_exists.js @@ -0,0 +1,31 @@ +// Copyright 2017 TODO Group. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +const commandExistsLib = require('command-exists') + +/** + * Checks whether or not a list of commands exists in the + * current environment. Returns the first command that was + * found to exist. + * + * @protected + * @param {string|string[]} command The command or commands to check for. + * @returns {string|null} The first command found to exist, or null of none were found. + */ +async function commandExists (command) { + // convert to array if needed + if (!Array.isArray(command)) { + command = [command] + } + for (const commandString of command) { + try { + await commandExistsLib(commandString) + return commandString + } catch (e) { + // do nothing + } + } + return null +} + +module.exports.commandExists = commandExists diff --git a/lib/licensee.js b/lib/licensee.js index faf5c0d6..acb5e504 100644 --- a/lib/licensee.js +++ b/lib/licensee.js @@ -1,7 +1,7 @@ // Copyright 2018 TODO Group. All rights reserved. // Licensed under the Apache License, Version 2.0. -const isWindows = require('is-windows') +const { commandExists } = require('./command_exists') const spawnSync = require('child_process').spawnSync class Licensee { @@ -11,14 +11,17 @@ class Licensee { * Throws 'Licensee not installed' error if command line of 'licensee' is not available. * * @param {string} targetDir The directory to run licensee on - * @returns {string[]} License identifiers + * @returns {Promise} License identifiers */ - identifyLicensesSync (targetDir) { - const licenseeOutput = spawnSync(isWindows() ? 'licensee.bat' : 'licensee', ['detect', '--json', targetDir]).stdout - if (licenseeOutput == null) { + async identifyLicense (targetDir) { + const command = await commandExists(['licensee', 'licensee.bat']) + if (command === null) { throw new Error('Licensee not installed') } - + const licenseeOutput = spawnSync(command, ['detect', '--json', targetDir]).stdout + if (licenseeOutput == null) { + throw new Error('Error executing licensee') + } const json = licenseeOutput.toString() return JSON.parse(json).licenses.map(function (license) { return license.spdx_id }) } diff --git a/lib/linguist.js b/lib/linguist.js index 56b65848..6d23b636 100644 --- a/lib/linguist.js +++ b/lib/linguist.js @@ -1,8 +1,8 @@ // Copyright 2017 TODO Group. All rights reserved. // SPDX-License-Identifier: Apache-2.0 -const isWindows = require('is-windows') const spawnSync = require('child_process').spawnSync +const { commandExists } = require('./command_exists') class Linguist { /** @@ -12,18 +12,20 @@ class Linguist { * Throws 'Linguist not installed' error if command line of 'linguist' is not available. * * @param {string} targetDir The directory to run linguist on - * @returns {object} The linguist output + * @returns {Promise} The linguist output */ - identifyLanguagesSync (targetDir) { + async identifyLanguages (targetDir) { // Command was renamed in https://github.com/github/linguist/pull/4208 - for (const command of ['github-linguist', 'linguist']) { - const output = spawnSync(isWindows() ? `${command}.bat` : command, [targetDir, '--json']).stdout - if (output !== null) { - return JSON.parse(output.toString()) - } + const command = await commandExists(['github-linguist', 'linguist', 'github-linguist.bat', 'linguist.bat']) + if (command === null) { + throw new Error('Linguist not installed') + } + const output = spawnSync(command, [targetDir, '--json']).stdout + if (output !== null) { + return JSON.parse(output.toString()) + } else { + throw new Error('Execution of linguist failed!') } - - throw new Error('Linguist not installed') } } diff --git a/tests/axioms/contributor_count_test.js b/tests/axioms/contributor_count_test.js index 0b5bcd49..c1842f1a 100644 --- a/tests/axioms/contributor_count_test.js +++ b/tests/axioms/contributor_count_test.js @@ -1,3 +1,6 @@ +// Copyright 2017 TODO Group. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + const chai = require('chai') const expect = chai.expect const path = require('path') diff --git a/tests/axioms/licensee_tests.js b/tests/axioms/licensee_tests.js index dba40535..5aa60b06 100644 --- a/tests/axioms/licensee_tests.js +++ b/tests/axioms/licensee_tests.js @@ -15,18 +15,18 @@ describe('licensee', function () { } else { const licenseeAxiom = require('../../axioms/licensee') - it('runs licensee', () => { + it('runs licensee', async () => { const mockFs = { targetDir: path.resolve(__dirname, '../../') } - const res = licenseeAxiom(mockFs) + const res = await licenseeAxiom(mockFs) expect(res.passed).to.equal(true) expect(res.targets).to.have.length(1) expect(res.targets[0].path).to.equal('Apache-2.0') }) - it('returns nothing if no licenses are found', () => { + it('returns nothing if no licenses are found', async () => { const mockFs = { targetDir: path.resolve(__dirname) } - const res = licenseeAxiom(mockFs) + const res = await licenseeAxiom(mockFs) expect(res.passed).to.equal(true) expect(res.targets).to.have.length(0) diff --git a/tests/axioms/linguist_tests.js b/tests/axioms/linguist_tests.js index ddc1818d..bec58e59 100644 --- a/tests/axioms/linguist_tests.js +++ b/tests/axioms/linguist_tests.js @@ -15,9 +15,9 @@ describe('linguist', function () { } else { const linguistAxiom = require('../../axioms/linguist') - it('runs linguist', () => { + it('runs linguist', async () => { const mockFs = { targetDir: path.resolve(__dirname, '../../') } - const res = linguistAxiom(mockFs) + const res = await linguistAxiom(mockFs) expect(res.passed).to.equal(true) expect(res.targets).to.have.length.greaterThan(0) diff --git a/tests/cli/cli_tests.js b/tests/cli/cli_tests.js index 52465030..f9055ad2 100644 --- a/tests/cli/cli_tests.js +++ b/tests/cli/cli_tests.js @@ -1,3 +1,6 @@ +// Copyright 2017 TODO Group. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + const path = require('path') const chai = require('chai') const cp = require('child_process') diff --git a/tests/lib/command_exists_tests.js b/tests/lib/command_exists_tests.js new file mode 100644 index 00000000..70d98ebc --- /dev/null +++ b/tests/lib/command_exists_tests.js @@ -0,0 +1,35 @@ +// Copyright 2017 TODO Group. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +const chai = require('chai') +const expect = chai.expect +const { commandExists } = require('../../lib/command_exists') + +describe('lib', () => { + describe('command_exists', function () { + it('should detect a command exists', async () => { + const res = await commandExists('ssh') + expect(res).to.equal('ssh') + }) + + it('should detect a command doesn\'t exists', async () => { + const res = await commandExists('notacommand') + expect(res).to.equal(null) + }) + + it('should detect one of the commands exist', async () => { + const res = await commandExists(['notacommand', 'ssh']) + expect(res).to.equal('ssh') + }) + + it('should detect none of the commands exist', async () => { + const res = await commandExists(['notacommand', 'alsonotacommand']) + expect(res).to.equal(null) + }) + + it('should detect the first command exists', async () => { + const res = await commandExists(['ssh', 'ln']) + expect(res).to.equal('ssh') + }) + }) +})