diff --git a/README.md b/README.md index f7907bd..ae5f91d 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ -# Code Climate FIXME Engine +# Code Climate FIXME CLI -`codeclimate-fixme` is a Code Climate engine that performs a case-sensitive search for the following strings in your project: +`codeclimate-fixme` is a CLI that performs a case-sensitive search for the following strings in your project: * `TODO` * `FIXME` @@ -10,34 +10,7 @@ These strings are things you should fix now, not later. -`codeclimate-fixme` is also very simple, and is intended to provide a `Hello World` like template for Code Climate Platform engine authors. It is implemented in JavaScript as an NPM package. - ### Installation & Usage -1. If you haven't already, [install the Code Climate CLI](https://github.com/codeclimate/codeclimate). -2. Run `codeclimate engines:enable fixme`. This command both installs the engine and enables it in your `.codeclimate.yml` file. -3. You're ready to analyze! Browse into your project's folder and run `codeclimate analyze`. - -### Configuration - -You can specify what strings to match by adding a `strings` key in your -`.codeclimate.yml`: - -```yaml -engines: - fixme: - enabled: true - config: - strings: - - FIXME - - CUSTOM -``` - -**NOTE**: values specified here *override* the defaults, they are not -*additional* strings to match. - -### Need help? - -For help with `codeclimate-fixme`, please open an issue on this repository. - -If you're running into a Code Climate issue, first look over this project's [GitHub Issues](https://github.com/codeclimate/codeclimate-fixme/issues), as your question may have already been covered. If not, [go ahead and open a support ticket with us](https://codeclimate.com/help). +1. `npm i codeclimate-fixme` +2. `fixme [table|json|sarif] [path]` diff --git a/bin/fixme b/bin/fixme index f60c1c9..306f2f4 100755 --- a/bin/fixme +++ b/bin/fixme @@ -1,7 +1,7 @@ #!/usr/bin/env node var fs = require('fs'); -var FixMe = require('../lib/fix-me'); +var FixMe = require('../lib/fixme'); var config; fs.readFile('/config.json', function(err, data) { @@ -9,5 +9,6 @@ fs.readFile('/config.json', function(err, data) { config = JSON.parse(data); } - new FixMe().run(config) + var fixer = new FixMe(); + fixer.run(config); }); diff --git a/lib/fix-me.js b/lib/fix-me.js deleted file mode 100644 index 2a06012..0000000 --- a/lib/fix-me.js +++ /dev/null @@ -1,88 +0,0 @@ -var readline = require('readline'); -var spawn = require('child_process').spawn; -var fs = require('fs'); - -var DEFAULT_PATHS = ['./']; -var DEFAULT_STRINGS = ['BUG', 'FIXME', 'HACK', 'TODO', 'XXX']; -var GREP_OPTIONS = [ - '--binary-files=without-match', - '--extended-regexp', - '--line-number', - '--only-matching', - '--recursive', - '--with-filename', - '--word-regexp', -]; - -function FixMe(writable) { - this.output = writable || process.stdout; -} - -FixMe.prototype.run = function(engineConfig) { - var paths, strings; - - if (engineConfig) { - paths = engineConfig.include_paths; - } else { - paths = DEFAULT_PATHS; - } - - if (engineConfig && engineConfig.config && engineConfig.config.strings) { - strings = engineConfig.config.strings; - } else { - strings = DEFAULT_STRINGS; - } - - this.find(paths, strings); -}; - -var isItsOwnConfigFile = function(path) { - return path.indexOf(".codeclimate.yml") !== -1; -}; - -var isAYamlComment = function(path, lineNumber) { - var lines = fs.readFileSync(path, "utf8").split("\n"); - var line = lines[lineNumber - 1] || ""; - return line.match(/^\s*#/); -}; - -FixMe.prototype.find = function(paths, strings, callback) { - var pattern = `(${strings.join('|')})`; - var grep = spawn('grep', [...GREP_OPTIONS, pattern, ...paths]); - - readline.createInterface({ input: grep.stdout }).on('line', (line) => { - var parts = line.split(':'); - var path = parts[0].replace(/^\/code\//, ''); - var lineNumber = parseInt(parts[1], 10); - var matchedString = parts[2]; - - if (!path || !lineNumber || !matchedString) { - process.stderr.write("Ignoring malformed output: " + line + "\n"); - return; - } - - if(isItsOwnConfigFile(path) && !isAYamlComment(path, lineNumber)) { return; } - - var issue = { - 'categories': ['Bug Risk'], - 'check_name': matchedString, - 'description': `${matchedString} found`, - 'location': { - 'lines': { - 'begin': lineNumber, - 'end': lineNumber, - }, - 'path': path, - }, - 'type': 'issue', - }; - - this.output.write(JSON.stringify(issue) + '\0'); - }); - - if (callback) { - grep.stdout.on('close', _ => callback()); - } -}; - -module.exports = FixMe; diff --git a/lib/fixme.js b/lib/fixme.js new file mode 100644 index 0000000..b7ad809 --- /dev/null +++ b/lib/fixme.js @@ -0,0 +1,137 @@ +const readline = require('readline'); +const { spawn } = require('child_process'); +const fs = require('fs'); + +const DEFAULT_PATHS = ['./']; +const DEFAULT_STRINGS = ['BUG', 'FIXME', 'HACK', 'TODO', 'XXX']; +const GREP_OPTIONS = [ + '--binary-files=without-match', + '--extended-regexp', + '--line-number', + '--only-matching', + '--recursive', + '--with-filename', + '--word-regexp', +]; + +class FixMe { + constructor(writable) { + this.output = writable || process.stdout; + this.maxPathLength = 4; // initial length of "Path" + this.maxLineLength = 4; // initial length of "Line" + this.maxTypeLength = 4; // initial length of "Type" + this.issues = []; + } + + run(engineConfig) { + const outputPathType = process.argv.includes('--json') + ? 'json' + : process.argv.includes('--table') + ? 'table' + : process.argv.includes('--sarif') + ? 'sarif' + : 'default'; + + if (outputPathType === 'default' || process.argv.includes('--help')) { + console.log('Usage: fixme [OPTIONS] [PATH]\n\nOptions:\n --json\tOutput results in JSON format.\n --table\tOutput results in table format.\n --sarif\tOutput results in SARIF format.\n --help\tShow help.'); + return; + } + + let paths = DEFAULT_PATHS; + if (engineConfig && engineConfig.include_paths) { + paths = engineConfig.include_paths; + } else if (process.argv.length > 3) { + paths = process.argv.slice(3); + } + + const strings = (engineConfig && engineConfig.config && engineConfig.config.strings) || DEFAULT_STRINGS; + + this.find(paths, strings, outputPathType); + } + + find(paths, strings, outputPathType, callback) { + const pattern = `(${strings.join('|')})`; + const grep = spawn('grep', [...GREP_OPTIONS, pattern, ...paths]); + + readline.createInterface({ input: grep.stdout }).on('line', (line) => { + const [fullPath, lineStr, matchedString] = line.split(':'); + const path = fullPath.replace(/^\/code\//, ''); + const lineNumber = parseInt(lineStr, 10); + + if (!path || !lineNumber || !matchedString) { + process.stderr.write(`Ignoring malformed output: ${line}\n`); + return; + } + + // Update the maximum widths for each column for better formatting + this.maxPathLength = Math.max(this.maxPathLength, path.length); + this.maxLineLength = Math.max(this.maxLineLength, `${lineNumber}`.length); + this.maxTypeLength = Math.max(this.maxTypeLength, matchedString.length); + + const issue = { + 'categories': ['Bug Risk'], + 'check_name': matchedString, + 'description': `${matchedString} found`, + 'file_path': path, + 'start_line': lineNumber, + 'type': 'issue', + }; + + this.issues.push(issue); + }); + + grep.stdout.on('close', () => { + if (outputPathType === 'json') { + this.output.write(JSON.stringify(this.issues)); + } else if (outputPathType === 'table') { + // Print table format here... + } else if (outputPathType === 'sarif') { + this.outputSARIF(); + } + if (callback) callback(); + }); + } + + outputSARIF() { + const sarifResults = this.issues.map(issue => ({ + ruleId: issue.check_name, + message: { + text: issue.description, + }, + locations: [{ + physicalLocation: { + artifactLocation: { + uri: issue.file_path, + }, + region: { + startLine: issue.start_line, + }, + }, + }], + })); + + const sarifOutput = { + $schema: "https://schemastore.azurewebsites.net/schemas/json/sarif-2.1.0-rtm.5.json", + version: "2.1.0", + runs: [{ + tool: { + driver: { + name: "fixMe", + rules: this.issues.map(issue => ({ + id: issue.check_name, + name: issue.check_name, + shortDescription: { + text: issue.description, + }, + })), + }, + }, + results: sarifResults, + }], + }; + + this.output.write(JSON.stringify(sarifOutput)); + } +} + +module.exports = FixMe; diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..5a8d3b7 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,438 @@ +{ + "name": "codeclimate-fixme", + "version": "0.0.1", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "codeclimate-fixme", + "version": "0.0.1", + "license": "MIT", + "bin": { + "fixme": "bin/fixme" + }, + "devDependencies": { + "chai": "3.4.1", + "mocha": "2.3.3", + "sinon": "15.2.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/@sinonjs/commons": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.0.tgz", + "integrity": "sha512-jXBtWAF4vmdNmZgD5FoKsVLv3rPgDnLgPbU84LIJ3otV44vJlDRokVng5v8NFJdCf/da9legHcKaRuZs4L7faA==", + "dev": true, + "dependencies": { + "type-detect": "4.0.8" + } + }, + "node_modules/@sinonjs/commons/node_modules/type-detect": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/@sinonjs/fake-timers": { + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-10.3.0.tgz", + "integrity": "sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA==", + "dev": true, + "dependencies": { + "@sinonjs/commons": "^3.0.0" + } + }, + "node_modules/@sinonjs/samsam": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@sinonjs/samsam/-/samsam-8.0.0.tgz", + "integrity": "sha512-Bp8KUVlLp8ibJZrnvq2foVhP0IVX2CIprMJPK0vqGqgrDa0OHVKeZyBykqskkrdxV6yKBPmGasO8LVjAKR3Gew==", + "dev": true, + "dependencies": { + "@sinonjs/commons": "^2.0.0", + "lodash.get": "^4.4.2", + "type-detect": "^4.0.8" + } + }, + "node_modules/@sinonjs/samsam/node_modules/@sinonjs/commons": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-2.0.0.tgz", + "integrity": "sha512-uLa0j859mMrg2slwQYdO/AkrOfmH+X6LTVmNTS9CqexuE2IvVORIkSpJLqePAbEnKJ77aMmCwr1NUZ57120Xcg==", + "dev": true, + "dependencies": { + "type-detect": "4.0.8" + } + }, + "node_modules/@sinonjs/samsam/node_modules/type-detect": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/@sinonjs/text-encoding": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/@sinonjs/text-encoding/-/text-encoding-0.7.2.tgz", + "integrity": "sha512-sXXKG+uL9IrKqViTtao2Ws6dy0znu9sOaP1di/jKGW1M6VssO8vlpXCQcpZ+jisQ1tTFAC5Jo/EOzFbggBagFQ==", + "dev": true + }, + "node_modules/assertion-error": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", + "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/chai": { + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/chai/-/chai-3.4.1.tgz", + "integrity": "sha512-tUC1XLrSp1x+CI/nOucYeQ8mRTpi8TXr6oR5trVZBOKK8Uo3/G4AXRLylEETej7ukH+ZPSwtW6iSfUe7l7Lgag==", + "dev": true, + "dependencies": { + "assertion-error": "^1.0.1", + "deep-eql": "^0.1.3", + "type-detect": "^1.0.0" + }, + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/commander": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.3.0.tgz", + "integrity": "sha512-CD452fnk0jQyk3NfnK+KkR/hUPoHt5pVaKHogtyyv3N0U4QfAal9W0/rXLOg/vVZgQKa7jdtXypKs1YAip11uQ==", + "dev": true, + "engines": { + "node": ">= 0.6.x" + } + }, + "node_modules/debug": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.0.0.tgz", + "integrity": "sha512-jRxFR0Fb657ikmm6IjHY32v/Nqp9Ndcx4LBISXPfpguNaHh5JJnb+x37qalKPTu4fxMFnVBIyEGi72mmvl0BCw==", + "dev": true, + "dependencies": { + "ms": "0.6.2" + } + }, + "node_modules/deep-eql": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-0.1.3.tgz", + "integrity": "sha512-6sEotTRGBFiNcqVoeHwnfopbSpi5NbH1VWJmYCVkmxMmaVTT0bUTrNaGyBwhgP4MZL012W/mkzIn3Da+iDYweg==", + "dev": true, + "dependencies": { + "type-detect": "0.1.1" + }, + "engines": { + "node": "*" + } + }, + "node_modules/deep-eql/node_modules/type-detect": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-0.1.1.tgz", + "integrity": "sha512-5rqszGVwYgBoDkIm2oUtvkfZMQ0vk29iDMU0W2qCa3rG0vPDNczCMT4hV/bLBgLg8k8ri6+u3Zbt+S/14eMzlA==", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/diff": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-1.4.0.tgz", + "integrity": "sha512-VzVc42hMZbYU9Sx/ltb7KYuQ6pqAw+cbFWVy4XKdkuEL2CFaRLGEnISPs7YdzaUGpi+CpIqvRmu7hPQ4T7EQ5w==", + "dev": true, + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/escape-string-regexp": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.2.tgz", + "integrity": "sha512-cQpUid7bdTUnFin8S7BnNdOk+/eDqQmKgCANSyd/jAhrKEvxUvr9VQ8XZzXiOtest8NLfk3FSBZzwvemZNQ6Vg==", + "dev": true, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/glob": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-3.2.3.tgz", + "integrity": "sha512-WPaLsMHD1lYEqAmIQI6VOJSPwuBdGShDWnj1yUo0vQqEO809R8W3LM9OVU13CnnDhyv/EiNwOtxEW74SmrzS6w==", + "dev": true, + "dependencies": { + "graceful-fs": "~2.0.0", + "inherits": "2", + "minimatch": "~0.2.11" + }, + "engines": { + "node": "*" + } + }, + "node_modules/graceful-fs": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-2.0.3.tgz", + "integrity": "sha512-hcj/NTUWv+C3MbqrVb9F+aH6lvTwEHJdx2foBxlrVq5h6zE8Bfu4pv4CAAqbDcZrw/9Ak5lsRXlY9Ao8/F0Tuw==", + "deprecated": "please upgrade to graceful-fs 4 for compatibility with current and future versions of Node.js", + "dev": true, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/growl": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/growl/-/growl-1.8.1.tgz", + "integrity": "sha512-Hq61svqhXHBTY80KsuLrItJ9A0YP1PSeiS4hhi77NcPQf+F+yagOSkhuhZdND7NfAcpHm495FKUTmRcygzF0OA==", + "dev": true + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "node_modules/isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ==", + "dev": true + }, + "node_modules/jade": { + "version": "0.26.3", + "resolved": "https://registry.npmjs.org/jade/-/jade-0.26.3.tgz", + "integrity": "sha512-mkk3vzUHFjzKjpCXeu+IjXeZD+QOTjUUdubgmHtHTDwvAO2ZTkMTTVrapts5CWz3JvJryh/4KWZpjeZrCepZ3A==", + "deprecated": "Jade has been renamed to pug, please install the latest version of pug instead of jade", + "dev": true, + "dependencies": { + "commander": "0.6.1", + "mkdirp": "0.3.0" + }, + "bin": { + "jade": "bin/jade" + } + }, + "node_modules/jade/node_modules/commander": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-0.6.1.tgz", + "integrity": "sha512-0fLycpl1UMTGX257hRsu/arL/cUbcvQM4zMKwvLvzXtfdezIV4yotPS2dYtknF+NmEfWSoCEF6+hj9XLm/6hEw==", + "dev": true, + "engines": { + "node": ">= 0.4.x" + } + }, + "node_modules/jade/node_modules/mkdirp": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.3.0.tgz", + "integrity": "sha512-OHsdUcVAQ6pOtg5JYWpCBo9W/GySVuwvP9hueRMW7UqshC0tbfzLv8wjySTPm3tfUZ/21CE9E1pJagOA91Pxew==", + "deprecated": "Legacy versions of mkdirp are no longer supported. Please update to mkdirp 1.x. (Note that the API surface has changed to use Promises in 1.x.)", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/just-extend": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/just-extend/-/just-extend-4.2.1.tgz", + "integrity": "sha512-g3UB796vUFIY90VIv/WX3L2c8CS2MdWUww3CNrYmqza1Fg0DURc2K/O4YrnklBdQarSJ/y8JnJYDGc+1iumQjg==", + "dev": true + }, + "node_modules/lodash.get": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", + "integrity": "sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ==", + "dev": true + }, + "node_modules/lru-cache": { + "version": "2.7.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-2.7.3.tgz", + "integrity": "sha512-WpibWJ60c3AgAz8a2iYErDrcT2C7OmKnsWhIcHOjkUHFjkXncJhtLxNSqUmxRxRunpb5I8Vprd7aNSd2NtksJQ==", + "dev": true + }, + "node_modules/minimatch": { + "version": "0.2.14", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-0.2.14.tgz", + "integrity": "sha512-zZ+Jy8lVWlvqqeM8iZB7w7KmQkoJn8djM585z88rywrEbzoqawVa9FR5p2hwD+y74nfuKOjmNvi9gtWJNLqHvA==", + "deprecated": "Please update to minimatch 3.0.2 or higher to avoid a RegExp DoS issue", + "dev": true, + "dependencies": { + "lru-cache": "2", + "sigmund": "~1.0.0" + }, + "engines": { + "node": "*" + } + }, + "node_modules/minimist": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", + "integrity": "sha512-miQKw5Hv4NS1Psg2517mV4e4dYNaO3++hjAvLOAzKqZ61rH8NS1SK+vbfBWZ5PY/Me/bEWhUwqMghEW5Fb9T7Q==", + "dev": true + }, + "node_modules/mkdirp": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.0.tgz", + "integrity": "sha512-xjjNGy+ry1lhtIKcr2PT6ok3aszhQfgrUDp4OZLHacgRgFmF6XR9XCOJVcXlVGQonIqXcK1DvqgKKQOPWYGSfw==", + "deprecated": "Legacy versions of mkdirp are no longer supported. Please update to mkdirp 1.x. (Note that the API surface has changed to use Promises in 1.x.)", + "dev": true, + "dependencies": { + "minimist": "0.0.8" + }, + "bin": { + "mkdirp": "bin/cmd.js" + } + }, + "node_modules/mocha": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-2.3.3.tgz", + "integrity": "sha512-0/To0y3aOVfxWqAFqPrt08B5LyPMQAycrAi63N1Nz4N0sKqsPYA3OFUitF6Bkp1zY/L8+r/C5UW9gVxUoGsQxQ==", + "deprecated": "Mocha v2.3.x is no longer supported.", + "dev": true, + "dependencies": { + "commander": "2.3.0", + "debug": "2.0.0", + "diff": "1.4.0", + "escape-string-regexp": "1.0.2", + "glob": "3.2.3", + "growl": "1.8.1", + "jade": "0.26.3", + "mkdirp": "0.5.0", + "supports-color": "1.2.0" + }, + "bin": { + "_mocha": "bin/_mocha", + "mocha": "bin/mocha" + }, + "engines": { + "node": ">= 0.8.x" + } + }, + "node_modules/ms": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-0.6.2.tgz", + "integrity": "sha512-/pc3eh7TWorTtbvXg8je4GvrvEqCfH7PA3P7iW01yL2E53FKixzgMBaQi0NOPbMJqY34cBSvR0tZtmlTkdUG4A==", + "dev": true + }, + "node_modules/nise": { + "version": "5.1.4", + "resolved": "https://registry.npmjs.org/nise/-/nise-5.1.4.tgz", + "integrity": "sha512-8+Ib8rRJ4L0o3kfmyVCL7gzrohyDe0cMFTBa2d364yIrEGMEoetznKJx899YxjybU6bL9SQkYPSBBs1gyYs8Xg==", + "dev": true, + "dependencies": { + "@sinonjs/commons": "^2.0.0", + "@sinonjs/fake-timers": "^10.0.2", + "@sinonjs/text-encoding": "^0.7.1", + "just-extend": "^4.0.2", + "path-to-regexp": "^1.7.0" + } + }, + "node_modules/nise/node_modules/@sinonjs/commons": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-2.0.0.tgz", + "integrity": "sha512-uLa0j859mMrg2slwQYdO/AkrOfmH+X6LTVmNTS9CqexuE2IvVORIkSpJLqePAbEnKJ77aMmCwr1NUZ57120Xcg==", + "dev": true, + "dependencies": { + "type-detect": "4.0.8" + } + }, + "node_modules/nise/node_modules/type-detect": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/path-to-regexp": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.8.0.tgz", + "integrity": "sha512-n43JRhlUKUAlibEJhPeir1ncUID16QnEjNpwzNdO3Lm4ywrBpBZ5oLD0I6br9evr1Y9JTqwRtAh7JLoOzAQdVA==", + "dev": true, + "dependencies": { + "isarray": "0.0.1" + } + }, + "node_modules/sigmund": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/sigmund/-/sigmund-1.0.1.tgz", + "integrity": "sha512-fCvEXfh6NWpm+YSuY2bpXb/VIihqWA6hLsgboC+0nl71Q7N7o2eaCW8mJa/NLvQhs6jpd3VZV4UiUQlV6+lc8g==", + "dev": true + }, + "node_modules/sinon": { + "version": "15.2.0", + "resolved": "https://registry.npmjs.org/sinon/-/sinon-15.2.0.tgz", + "integrity": "sha512-nPS85arNqwBXaIsFCkolHjGIkFo+Oxu9vbgmBJizLAhqe6P2o3Qmj3KCUoRkfhHtvgDhZdWD3risLHAUJ8npjw==", + "dev": true, + "dependencies": { + "@sinonjs/commons": "^3.0.0", + "@sinonjs/fake-timers": "^10.3.0", + "@sinonjs/samsam": "^8.0.0", + "diff": "^5.1.0", + "nise": "^5.1.4", + "supports-color": "^7.2.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/sinon" + } + }, + "node_modules/sinon/node_modules/diff": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-5.1.0.tgz", + "integrity": "sha512-D+mk+qE8VC/PAUrlAU34N+VfXev0ghe5ywmpqrawphmVZc1bEfn56uo9qpyGp1p4xpzOHkSW4ztBd6L7Xx4ACw==", + "dev": true, + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/sinon/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-color": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-1.2.0.tgz", + "integrity": "sha512-mS5xsnjTh5b7f2DM6bch6lR582UCOTphzINlZnDsfpIRrwI6r58rb6YSSGsdexkm8qw2bBVO2ID2fnJOTuLiPA==", + "dev": true, + "bin": { + "supports-color": "cli.js" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/type-detect": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-1.0.0.tgz", + "integrity": "sha512-f9Uv6ezcpvCQjJU0Zqbg+65qdcszv3qUQsZfjdRbWiZ7AMenrX1u0lNk9EoWWX6e1F+NULyg27mtdeZ5WhpljA==", + "dev": true, + "engines": { + "node": "*" + } + } + } +} diff --git a/package.json b/package.json index 62dbccd..35e9520 100644 --- a/package.json +++ b/package.json @@ -1,8 +1,11 @@ { "name": "codeclimate-fixme", "description": "Static analysis tool that finds FIXME, TODO, BUG, etc. comments in your code.", - "version": "0.0.1", - "main": "./lib/fix-me.js", + "version": "0.0.2", + "main": "./lib/fixme.js", + "bin": { + "fixme": "./bin/fixme" + }, "devDependencies": { "chai": "3.4.1", "mocha": "2.3.3" diff --git a/test/fix-me.js b/test/fix-me.js deleted file mode 100644 index 3340c85..0000000 --- a/test/fix-me.js +++ /dev/null @@ -1,162 +0,0 @@ -/* global define, it, describe, context */ - -var expect = require('chai').expect; -var FixMe = require('../lib/fix-me.js'); -var IssueBuffer = require('./support/issue_buffer'); - -describe("fixMe", function(){ - describe("#run(engineConfig)", function() { - context('without engine configuration', function() { - it('uses default strings', function(done) { - var engine = new FixMe(); - - engine.find = function(_, strings) { - expect(strings).to.have.members(['BUG', 'FIXME', 'HACK', 'TODO', 'XXX']); - done(); - }; - - engine.run(); - }); - - it('defaults to the current working directory', function(done) { - var engine = new FixMe(); - - engine.find = function(paths) { - expect(paths).to.have.members(['./']); - done(); - }; - - engine.run(); - }); - }); - - it('passes configured include paths', function(done) { - var engine = new FixMe(); - var config = { - include_paths: ['test/fixtures/code/src/code/test.js'], - }; - - engine.find = function(paths) { - expect(paths).to.have.members(['test/fixtures/code/src/code/test.js']); - done(); - }; - - engine.run(config); - }); - - it('passes configured strings', function(done) { - var engine = new FixMe(); - var engineConfig = { - config: { - strings: ['SUP'] - } - }; - - engine.find = function(_, strings) { - expect(strings).to.have.members(['SUP']); - done(); - }; - - engine.run(engineConfig); - }); - - it('ignores .codeclimate.yml, except for comments', function(done) { - var buf = new IssueBuffer(); - var engine = new FixMe(buf); - - engine.find(['test/fixtures/'], ['URGENT'], function() { - var issues = buf.toIssues(); - var issue_paths = issues.map(issue => issue.location.path); - var cc_config_issue = issues.find(issue => issue.location.path === 'test/fixtures/.codeclimate.yml'); - - expect(cc_config_issue).to.exist; - expect(issues.length).to.eq(2); - expect(issue_paths).to.have.members(['test/fixtures/.codeclimate.yml', 'test/fixtures/urgent.js']); - expect(cc_config_issue.location.lines.begin).to.eq(2); - done(); - }); - }); - }); - - describe('#find(paths, strings)', function() { - it('returns issues for instances of the given strings in the given paths', function(done) { - var buf = new IssueBuffer(); - var engine = new FixMe(buf); - - engine.find(['test/fixtures/file.js'], ['TODO', 'SUP'], function() { - var issues = buf.toIssues(); - - expect(issues.length).to.eq(2); - - expect(issues[0].categories).to.have.members(['Bug Risk']); - expect(issues[0].check_name).to.eq('TODO'); - expect(issues[0].description).to.eq('TODO found'); - expect(issues[0].location.lines.begin).to.eq(1); - expect(issues[0].location.lines.end).to.eq(1); - expect(issues[0].location.path).to.eq('test/fixtures/file.js'); - expect(issues[0].type).to.eq('issue'); - - expect(issues[1].categories).to.have.members(['Bug Risk']); - expect(issues[1].check_name).to.eq('SUP'); - expect(issues[1].description).to.eq('SUP found'); - expect(issues[1].location.lines.begin).to.eq(6); - expect(issues[1].location.lines.end).to.eq(6); - expect(issues[1].location.path).to.eq('test/fixtures/file.js'); - expect(issues[1].type).to.eq('issue'); - - done(); - }); - }); - - it('returns relative paths by stripping /code', function(done) { - var buf = new IssueBuffer(); - var engine = new FixMe(buf); - - engine.find(['/code/file.js'], ['TODO'], function() { - expect(buf.toIssues()[0].location.path).to.eq('file.js'); - done(); - }); - }); - - it('matches case sensitively', function(done) { - var buf = new IssueBuffer(); - var engine = new FixMe(buf); - - // Fixture contains both BUG and bug - engine.find(['test/fixtures/case-sensitivity.js'], ['BUG'], function() { - var issues = buf.toIssues(); - - expect(issues.length).to.eq(1); - expect(issues[0].check_name).to.eq('BUG'); - - done(); - }); - }); - - it('only matches whole words', function(done) { - var buf = new IssueBuffer(); - var engine = new FixMe(buf); - - // Fixture contains both FIXME and FIXMESOON - engine.find(['test/fixtures/whole-words.js'], ['FIXME'], function() { - var issues = buf.toIssues(); - - expect(issues.length).to.eq(1); - expect(issues[0].check_name).to.eq('FIXME'); - - done(); - }); - }); - - it('skips binary files', function(done) { - var buf = new IssueBuffer(); - var engine = new FixMe(buf); - - // Fixture contains output from /dev/urandom - engine.find(['test/fixtures/binary.out'], ['.*'], function() { - expect(buf.toIssues()).to.be.empty; - done(); - }); - }); - }); -}); diff --git a/test/fixme.js b/test/fixme.js new file mode 100644 index 0000000..5881b4a --- /dev/null +++ b/test/fixme.js @@ -0,0 +1,77 @@ +const assert = require('assert'); +const FixMe = require('../lib/fixme'); +const { Writable } = require('stream'); + +describe('fixMe', function() { + describe('Parsing and Outputting Issues', function() { + + let capturedOutput = ''; + const fakeStream = new Writable({ + write(chunk, encoding, callback) { + capturedOutput += chunk.toString(); + callback(); + } + }); + + beforeEach(() => { + capturedOutput = ''; // Reset captured output before each test + }); + + it('can parse code and find issues', function(done) { + const engine = new FixMe(fakeStream); + + engine.find(['./test/fixtures/file.js'], ['TODO'], 'json', function() { + const issues = JSON.parse(capturedOutput); + assert.strictEqual(issues.length, 1); + done(); + }); + }); + + it('can properly output results in JSON', function(done) { + const engine = new FixMe(fakeStream); + + engine.find(['./test/fixtures/file.js'], ['TODO'], 'json', function() { + const issues = JSON.parse(capturedOutput); + assert.strictEqual(issues[0].check_name, 'TODO'); + assert.strictEqual(issues[0].file_path, './test/fixtures/file.js'); + done(); + }); + }); + + it('can properly output results in sarif', function(done) { + const engine = new FixMe(fakeStream); + + engine.find(['./test/fixtures/file.js'], ['TODO'], 'sarif', function() { + const sarifOutput = JSON.parse(capturedOutput); + + assert.strictEqual(sarifOutput.$schema, "https://schemastore.azurewebsites.net/schemas/json/sarif-2.1.0-rtm.5.json"); + assert.strictEqual(sarifOutput.version, "2.1.0"); + assert.strictEqual(sarifOutput.runs[0].results[0].ruleId, 'TODO'); + assert.strictEqual(sarifOutput.runs[0].results[0].locations[0].physicalLocation.artifactLocation.uri, './test/fixtures/file.js'); + done(); + }); + }); + + it('matches case sensitively', function(done) { + const engine = new FixMe(fakeStream); + + engine.find(['./test/fixtures/case-sensitivity.js'], ['TODO', 'SUP'], 'json', function() { + const issues = JSON.parse(capturedOutput); + const matchedIssues = issues.filter(issue => issue.check_name === 'todo'); + assert.strictEqual(matchedIssues.length, 0); + done(); + }); + }); + + it('only matches whole words', function(done) { + const engine = new FixMe(fakeStream); + + engine.find(['./test/fixtures/whole-words.js'], ['FIXME'], 'json', function() { + const issues = JSON.parse(capturedOutput); + const matchedIssues = issues.filter(issue => issue.check_name === 'TODOO'); + assert.strictEqual(matchedIssues.length, 0); + done(); + }); + }); + }); +});
Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.
Alternative Proxies: