From ebffc913e9102adc12f3da50916b6a71cc2bb60f Mon Sep 17 00:00:00 2001 From: mrb Date: Wed, 26 Aug 2015 10:26:54 -0400 Subject: [PATCH 01/44] Make Dockerfile spec compliant --- Dockerfile | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 0dacb6a..bcb85c8 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,9 +1,15 @@ FROM node -WORKDIR /usr/src/app +MAINTAINER Michael R. Bernstein + +RUN useradd -u 9000 -r -s /bin/false app RUN npm install glob +WORKDIR /code COPY . /usr/src/app +USER app +VOLUME /code + CMD ["/usr/src/app/bin/fixme"] From cee6922ca606ad86d2567e4b2a5e2572555002ef Mon Sep 17 00:00:00 2001 From: Will Fleming Date: Thu, 17 Sep 2015 14:31:22 -0400 Subject: [PATCH 02/44] We're hiring! --- WERE_HIRING.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 WERE_HIRING.md diff --git a/WERE_HIRING.md b/WERE_HIRING.md new file mode 100644 index 0000000..bc97549 --- /dev/null +++ b/WERE_HIRING.md @@ -0,0 +1,5 @@ +# Code Climate is Hiring + +Thanks for checking our our CLI. Since you found your way here, you may be interested in working on open source, and building awesome tools for developers. If so, you should check out our open jobs: + +#### http://jobs.codeclimate.com/ From c94d1db15bab064ad79045bd4c93e81a34d38c72 Mon Sep 17 00:00:00 2001 From: Jacek Kubiak Date: Tue, 29 Sep 2015 16:47:02 +0200 Subject: [PATCH 03/44] Modify index.js to support include_paths --- index.js | 74 ++++++++++++++++++++++++++++++++++++++------------------ 1 file changed, 50 insertions(+), 24 deletions(-) diff --git a/index.js b/index.js index e73b976..a20dc09 100644 --- a/index.js +++ b/index.js @@ -23,7 +23,7 @@ var printIssue = function(fileName, lineNum, matchedString){ "path": fileName, "lines": { "begin": lineNum, - "end": lineNum + "end": lineNum } } }; @@ -44,7 +44,7 @@ var findFixmes = function(file){ if (results !== ""){ // Parses grep output var lines = results.split("\n"); - + lines.forEach(function(line, index, array){ // grep spits out an extra line that we can ignore if(index < (array.length-1)){ @@ -65,39 +65,65 @@ var findFixmes = function(file){ }) } -var eligibleFile = function(fp, excludePaths){ - return (excludePaths.indexOf(fp.split("/code/")[1]) < 0) && - !fs.lstatSync(fp).isDirectory() && - (excludeExtensions.indexOf(path.extname(fp)) < 0) +// Returns an array of unique array values not included in the other provided array +var diff = function(a1, a2) { + var result = []; + + for (var i = 0; i < a1.length; i++) { + if (a2.indexOf(a1[i]) === -1) { + result.push(a1[i]); + } + + } + return result; } -// Uses glob to traverse code directory and find files to analyze, -// excluding files passed in with by CLI config -var fileWalk = function(excludePaths){ - var analysisFiles = []; - var allFiles = glob.sync("/code/**/**", {}); +// Returns all the file paths in the main directory that match the given pattern +var buildFiles = function(paths) { + var files = []; - allFiles.forEach(function(file, i, a){ - if(eligibleFile(file, excludePaths)){ - analysisFiles.push(file); - } + paths.forEach(function(path, i, a) { + var pattern = "/code/" + path + "**" + files.push.apply(files, glob.sync(pattern, {})); + }); + + return files; +} + +// Filters the directory paths out +var filterFiles = function(files) { + return files.filter(function(file) { + return !fs.lstatSync(file).isDirectory(); }); - - return analysisFiles; +} + +// Returns file paths based on the exclude_paths values in config file +var buildFilesWithExclusions = function(exclusions) { + var allFiles = glob.sync("/code/**/**", {}); + var excludedFiles = buildFiles(exclusions); + + return diff(allFiles, excludedFiles); +} + +// Returns file paths based on the include_paths values in config file +var buildFilesWithInclusions = function(inclusions) { + return buildFiles(inclusions); } FixMe.prototype.runEngine = function(){ - // Check for existence of config.json, parse exclude paths if it exists + var analysisFiles = [] + if (fs.existsSync("/config.json")) { var engineConfig = JSON.parse(fs.readFileSync("/config.json")); - var excludePaths = engineConfig.exclude_paths; - } else { - var excludePaths = []; - } - // Walk /code/ path and find files to analyze - var analysisFiles = fileWalk(excludePaths); + if (engineConfig.hasOwnProperty("include_paths")) { + analysisFiles = buildFilesWithInclusions(engineConfig.include_paths); + } else if (engineConfig.hasOwnProperty("exclude_paths")) { + analysisFiles = buildFilesWithExclusions(engineConfig.exclude_paths); + } + } + analysisFiles = filterFiles(analysisFiles); // Execute main loop and find fixmes in valid files analysisFiles.forEach(function(f, i, a){ findFixmes(f); From e48a3138319d889854aa863ef9796efe7673976f Mon Sep 17 00:00:00 2001 From: ABaldwinHunter Date: Thu, 8 Oct 2015 10:43:02 -0400 Subject: [PATCH 04/44] update registry reference for circle This change should allow tests to pass on PRs from forks And fix https://github.com/codeclimate/codeclimate-fixme/pull/8 --- circle.yml | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/circle.yml b/circle.yml index c51658a..012fc78 100644 --- a/circle.yml +++ b/circle.yml @@ -3,21 +3,23 @@ machine: - docker environment: CLOUDSDK_CORE_DISABLE_PROMPTS: 1 - image_name: codeclimate-fixme - -dependencies: - pre: - - echo $gcloud_json_key_base64 | sed 's/ //g' | base64 -d > /tmp/gcloud_key.json - - curl https://sdk.cloud.google.com | bash - - gcloud auth activate-service-account $gcloud_account_email --key-file /tmp/gcloud_key.json - - gcloud docker -a + PRIVATE_REGISTRY: us.gcr.io/code_climate test: override: - - docker build -t=$registry_root/$image_name:b$CIRCLE_BUILD_NUM . + - docker build -t=$PRIVATE_REGISTRY/$CIRCLE_PROJECT_REPONAME:b$CIRCLE_BUILD_NUM . deployment: registry: branch: master + owner: codeclimate commands: - - docker push $registry_root/$image_name:b$CIRCLE_BUILD_NUM + - echo $GCLOUD_JSON_KEY_BASE64 | sed 's/ //g' | base64 -d > /tmp/gcloud_key.json + - curl https://sdk.cloud.google.com | bash + - gcloud auth activate-service-account --key-file /tmp/gcloud_key.json + - gcloud docker -a + - docker push $PRIVATE_REGISTRY/$CIRCLE_PROJECT_REPONAME:b$CIRCLE_BUILD_NUM + +notify: + webhooks: + - url: https://cc-slack-proxy.herokuapp.com/circle From b84763b8d190ced56d1dddfb8df805c12c161343 Mon Sep 17 00:00:00 2001 From: ABaldwinHunter Date: Thu, 8 Oct 2015 10:43:02 -0400 Subject: [PATCH 05/44] update registry reference for circle This change should allow tests to pass on PRs from forks And fix https://github.com/codeclimate/codeclimate-fixme/pull/8 --- circle.yml | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/circle.yml b/circle.yml index c51658a..012fc78 100644 --- a/circle.yml +++ b/circle.yml @@ -3,21 +3,23 @@ machine: - docker environment: CLOUDSDK_CORE_DISABLE_PROMPTS: 1 - image_name: codeclimate-fixme - -dependencies: - pre: - - echo $gcloud_json_key_base64 | sed 's/ //g' | base64 -d > /tmp/gcloud_key.json - - curl https://sdk.cloud.google.com | bash - - gcloud auth activate-service-account $gcloud_account_email --key-file /tmp/gcloud_key.json - - gcloud docker -a + PRIVATE_REGISTRY: us.gcr.io/code_climate test: override: - - docker build -t=$registry_root/$image_name:b$CIRCLE_BUILD_NUM . + - docker build -t=$PRIVATE_REGISTRY/$CIRCLE_PROJECT_REPONAME:b$CIRCLE_BUILD_NUM . deployment: registry: branch: master + owner: codeclimate commands: - - docker push $registry_root/$image_name:b$CIRCLE_BUILD_NUM + - echo $GCLOUD_JSON_KEY_BASE64 | sed 's/ //g' | base64 -d > /tmp/gcloud_key.json + - curl https://sdk.cloud.google.com | bash + - gcloud auth activate-service-account --key-file /tmp/gcloud_key.json + - gcloud docker -a + - docker push $PRIVATE_REGISTRY/$CIRCLE_PROJECT_REPONAME:b$CIRCLE_BUILD_NUM + +notify: + webhooks: + - url: https://cc-slack-proxy.herokuapp.com/circle From faabb01d0b295bf1c98544b8dc59515a9d534877 Mon Sep 17 00:00:00 2001 From: Bryan Helmkamp Date: Fri, 6 Nov 2015 04:04:12 -0500 Subject: [PATCH 06/44] Add test suite --- Dockerfile | 3 ++- engine.json | 11 +++++++++++ index.js | 10 +++++----- test/integration/fixme.rb | 41 +++++++++++++++++++++++++++++++++++++++ 4 files changed, 59 insertions(+), 6 deletions(-) create mode 100644 engine.json create mode 100644 test/integration/fixme.rb diff --git a/Dockerfile b/Dockerfile index bcb85c8..1aecb17 100644 --- a/Dockerfile +++ b/Dockerfile @@ -2,12 +2,13 @@ FROM node MAINTAINER Michael R. Bernstein -RUN useradd -u 9000 -r -s /bin/false app +RUN useradd -u 9000 -r -s /bin/false app RUN npm install glob WORKDIR /code COPY . /usr/src/app +COPY engine.json / USER app VOLUME /code diff --git a/engine.json b/engine.json new file mode 100644 index 0000000..a37f3c9 --- /dev/null +++ b/engine.json @@ -0,0 +1,11 @@ +{ + "name": "fixme", + "description": "Discover lurking FIXMEs, BUGs, TODOs, etc. in your code", + "maintainer": { + "name": "Michael R. Bernstein", + "email": "mrb@codeclimate.com" + }, + "test_paths": [ + "/usr/src/app/test/integration/" + ] +} diff --git a/index.js b/index.js index e73b976..ad1a2db 100644 --- a/index.js +++ b/index.js @@ -16,14 +16,14 @@ var excludeExtensions = [".jpg", ".jpeg", ".png", ".gif"]; var printIssue = function(fileName, lineNum, matchedString){ var issue = { "type": "issue", - "check_name": "FIXME found", + "check_name": matchedString, "description": matchedString + " found", "categories": ["Bug Risk"], "location":{ "path": fileName, "lines": { "begin": lineNum, - "end": lineNum + "end": lineNum } } }; @@ -44,7 +44,7 @@ var findFixmes = function(file){ if (results !== ""){ // Parses grep output var lines = results.split("\n"); - + lines.forEach(function(line, index, array){ // grep spits out an extra line that we can ignore if(index < (array.length-1)){ @@ -57,7 +57,7 @@ var findFixmes = function(file){ var matchedString = cols[2]; if (matchedString !== undefined){ - printIssue(fileName, lineNum, matchedString); + printIssue(fileName, parseInt(lineNum), matchedString); } } }) @@ -82,7 +82,7 @@ var fileWalk = function(excludePaths){ analysisFiles.push(file); } }); - + return analysisFiles; } diff --git a/test/integration/fixme.rb b/test/integration/fixme.rb new file mode 100644 index 0000000..fa5c42c --- /dev/null +++ b/test/integration/fixme.rb @@ -0,0 +1,41 @@ +##################################################################### +##################################################################### +## Integration tests for this engine must escape uses of the magic +## keywords, otherwise the engine will flag additional issues on the +## lines with the test markers themselves. We use backslash escaping +## to avoid this problem. +##################################################################### +##################################################################### + +# [issue] check_name="F\IXME" description="F\IXME found" category="Bu\g Risk" +# FIXME: This is broken +x = x + +############################ + +foo = $stdin.gets +# [issue] check_name="B\UG" description="B\UG found" category="Bu\g Risk" +# BUG: This should use double equals +if (foo = "foo") + puts "You said foo" +end + +############################ + +foo = $stdin.gets +# [issue] check_name="X\XX" description="X\XX found" category="Bu\g Risk" +# XXX: This should use double equals +if (foo = "foo") + puts "You said foo" +end + +############################ + +# [issue] check_name="T\ODO" description="T\ODO found" category="Bu\g Risk" +# TODO: Add more tests + +############################ + +# [issue] check_name="H\ACK" description="H\ACK found" category="Bu\g Risk" +# HACK +instance_eval "CRAZY STUFF" From cead1e5aa5003aad66fbdf2f1b6911644b3bde35 Mon Sep 17 00:00:00 2001 From: John Pignata Date: Fri, 6 Nov 2015 22:22:27 -0500 Subject: [PATCH 07/44] Push to new Docker registry --- circle.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/circle.yml b/circle.yml index 012fc78..f2e5d74 100644 --- a/circle.yml +++ b/circle.yml @@ -4,6 +4,9 @@ machine: environment: CLOUDSDK_CORE_DISABLE_PROMPTS: 1 PRIVATE_REGISTRY: us.gcr.io/code_climate + CODECLIMATE_DOCKER_REGISTRY_HOSTNAME: registry.codeclimate.net + CODECLIMATE_DOCKER_REGISTRY_USERNAME: circleci + CODECLIMATE_DOCKER_REGISTRY_EMAIL: ops@codeclimate.com test: override: @@ -19,6 +22,9 @@ deployment: - gcloud auth activate-service-account --key-file /tmp/gcloud_key.json - gcloud docker -a - docker push $PRIVATE_REGISTRY/$CIRCLE_PROJECT_REPONAME:b$CIRCLE_BUILD_NUM + - docker login --username=$CODECLIMATE_DOCKER_REGISTRY_USERNAME --password=$CODECLIMATE_DOCKER_REGISTRY_PASSWORD --email=$CODECLIMATE_DOCKER_REGISTRY_EMAIL $CODECLIMATE_DOCKER_REGISTRY_HOSTNAME + - docker tag $PRIVATE_REGISTRY/$CIRCLE_PROJECT_REPONAME:b$CIRCLE_BUILD_NUM $CODECLIMATE_DOCKER_REGISTRY_HOSTNAME/$CIRCLE_PROJECT_USERNAME/$CIRCLE_PROJECT_REPONAME:b$CIRCLE_BUILD_NUM + - docker push $CODECLIMATE_DOCKER_REGISTRY_HOSTNAME/$CIRCLE_PROJECT_USERNAME/$CIRCLE_PROJECT_REPONAME:b$CIRCLE_BUILD_NUM notify: webhooks: From fef0d9f68c908bdb36be7eeb5fe9dcb06fb38351 Mon Sep 17 00:00:00 2001 From: ABaldwinHunter Date: Thu, 12 Nov 2015 12:42:32 -0500 Subject: [PATCH 08/44] Add test suite and package as node module --- .codeclimate.yml | 8 +- .eslintrc | 253 +++++++++++++++++++++++++++++++++++++++++++ Dockerfile | 4 +- bin/fixme | 2 +- circle.yml | 7 +- index.js | 133 ----------------------- lib/diff.js | 13 +++ lib/file-builder.js | 41 +++++++ lib/fix-me.js | 86 +++++++++++++++ package.json | 34 ++++++ test/diff.js | 14 +++ test/file-builder.js | 20 ++++ test/fix-me.js | 15 +++ test/test.js | 6 - 14 files changed, 493 insertions(+), 143 deletions(-) create mode 100644 .eslintrc delete mode 100644 index.js create mode 100644 lib/diff.js create mode 100644 lib/file-builder.js create mode 100644 lib/fix-me.js create mode 100644 package.json create mode 100644 test/diff.js create mode 100644 test/file-builder.js create mode 100644 test/fix-me.js delete mode 100644 test/test.js diff --git a/.codeclimate.yml b/.codeclimate.yml index 6a739d8..96a5552 100644 --- a/.codeclimate.yml +++ b/.codeclimate.yml @@ -1,8 +1,14 @@ engines: fixme: + enabled: false + eslint: enabled: true +ratings: + paths: + - "**.js" exclude_paths: - "**/*.md" - "Dockerfile" - "bin/fixme" -- "index.js" +- "tests/**" +- ".codeclimate.yml" diff --git a/.eslintrc b/.eslintrc new file mode 100644 index 0000000..233f4e7 --- /dev/null +++ b/.eslintrc @@ -0,0 +1,253 @@ +ecmaFeatures: {} +rules: + no-alert: 0 + no-array-constructor: 0 + no-bitwise: 0 + no-caller: 0 + no-catch-shadow: 0 + no-class-assign: 0 + no-cond-assign: 2 + no-console: 2 + no-const-assign: 0 + no-constant-condition: 2 + no-continue: 0 + no-control-regex: 2 + no-debugger: 2 + no-delete-var: 2 + no-div-regex: 0 + no-dupe-keys: 2 + no-dupe-args: 2 + no-duplicate-case: 2 + no-else-return: 0 + no-empty: 2 + no-empty-character-class: 2 + no-empty-label: 0 + no-eq-null: 0 + no-eval: 0 + no-ex-assign: 2 + no-extend-native: 0 + no-extra-bind: 0 + no-extra-boolean-cast: 2 + no-extra-parens: 0 + no-extra-semi: 2 + no-fallthrough: 2 + no-floating-decimal: 0 + no-func-assign: 2 + no-implicit-coercion: 0 + no-implied-eval: 0 + no-inline-comments: 0 + no-inner-declarations: + - 2 + - functions + no-invalid-regexp: 2 + no-invalid-this: 0 + no-irregular-whitespace: 2 + no-iterator: 0 + no-label-var: 0 + no-labels: 0 + no-lone-blocks: 0 + no-lonely-if: 0 + no-loop-func: 0 + no-mixed-requires: + - 0 + - false + no-mixed-spaces-and-tabs: + - 2 + - false + linebreak-style: + - 0 + - unix + no-multi-spaces: 0 + no-multi-str: 0 + no-multiple-empty-lines: + - 0 + - max: 2 + no-native-reassign: 0 + no-negated-in-lhs: 2 + no-nested-ternary: 0 + no-new: 0 + no-new-func: 0 + no-new-object: 0 + no-new-require: 0 + no-new-wrappers: 0 + no-obj-calls: 2 + no-octal: 2 + no-octal-escape: 0 + no-param-reassign: 0 + no-path-concat: 0 + no-plusplus: 0 + no-process-env: 0 + no-process-exit: 0 + no-proto: 0 + no-redeclare: 2 + no-regex-spaces: 2 + no-reserved-keys: 0 + no-restricted-modules: 0 + no-return-assign: 0 + no-script-url: 0 + no-self-compare: 0 + no-sequences: 0 + no-shadow: 0 + no-shadow-restricted-names: 0 + no-spaced-func: 0 + no-sparse-arrays: 2 + no-sync: 0 + no-ternary: 0 + no-trailing-spaces: 0 + no-this-before-super: 0 + no-throw-literal: 0 + no-undef: 2 + no-undef-init: 0 + no-undefined: 0 + no-unexpected-multiline: 0 + no-underscore-dangle: 0 + no-unneeded-ternary: 0 + no-unreachable: 2 + no-unused-expressions: 0 + no-unused-vars: + - 2 + - vars: all + args: after-used + no-use-before-define: 0 + no-useless-call: 0 + no-void: 0 + no-var: 0 + no-warning-comments: + - 0 + - terms: + - todo + - fixme + - xxx + location: start + no-with: 0 + array-bracket-spacing: + - 0 + - never + arrow-parens: 0 + arrow-spacing: 0 + accessor-pairs: 0 + block-scoped-var: 0 + brace-style: + - 0 + - 1tbs + callback-return: 0 + camelcase: 0 + comma-dangle: + - 2 + - never + comma-spacing: 0 + comma-style: 0 + complexity: + - 2 + - 11 + computed-property-spacing: + - 0 + - never + consistent-return: 0 + consistent-this: + - 0 + - that + constructor-super: 0 + curly: + - 0 + - all + default-case: 0 + dot-location: 0 + dot-notation: + - 0 + - allowKeywords: true + eol-last: 0 + eqeqeq: 0 + func-names: 0 + func-style: + - 0 + - declaration + generator-star-spacing: 0 + guard-for-in: 0 + handle-callback-err: 0 + indent: 0 + init-declarations: 0 + key-spacing: + - 0 + - beforeColon: false + afterColon: true + lines-around-comment: 0 + max-depth: + - 0 + - 4 + max-len: + - 0 + - 80 + - 4 + max-nested-callbacks: + - 0 + - 2 + max-params: + - 0 + - 3 + max-statements: + - 0 + - 10 + new-cap: 0 + new-parens: 0 + newline-after-var: 0 + object-curly-spacing: + - 0 + - never + object-shorthand: 0 + one-var: 0 + operator-assignment: + - 0 + - always + operator-linebreak: 0 + padded-blocks: 0 + prefer-const: 0 + prefer-spread: 0 + prefer-reflect: 0 + quote-props: 0 + quotes: + - 0 + - double + radix: 0 + require-yield: 0 + semi: 0 + semi-spacing: + - 0 + - before: false + after: true + sort-vars: 0 + space-after-keywords: + - 0 + - always + space-before-blocks: + - 0 + - always + space-before-function-paren: + - 0 + - always + space-in-parens: + - 0 + - never + space-infix-ops: 0 + space-return-throw-case: 0 + space-unary-ops: + - 0 + - words: true + nonwords: false + spaced-comment: 0 + strict: 0 + use-isnan: 2 + valid-jsdoc: 0 + valid-typeof: 2 + vars-on-top: 0 + wrap-iife: 0 + wrap-regex: 0 + yoda: + - 0 + - never +env: + browser: true + node: true + jquery: true + amd: true + commonjs: true diff --git a/Dockerfile b/Dockerfile index 1aecb17..281bff9 100644 --- a/Dockerfile +++ b/Dockerfile @@ -4,7 +4,9 @@ MAINTAINER Michael R. Bernstein RUN useradd -u 9000 -r -s /bin/false app -RUN npm install glob +ENV NODE_ENV production + +RUN npm install WORKDIR /code COPY . /usr/src/app diff --git a/bin/fixme b/bin/fixme index e5c41e2..6f6d016 100755 --- a/bin/fixme +++ b/bin/fixme @@ -1,6 +1,6 @@ #!/usr/bin/env node -var FixMe = require('../index'); +var FixMe = require('../lib/fix-me'); var fixMe = new FixMe(); fixMe.runEngine(); diff --git a/circle.yml b/circle.yml index f2e5d74..63d9dec 100644 --- a/circle.yml +++ b/circle.yml @@ -8,9 +8,14 @@ machine: CODECLIMATE_DOCKER_REGISTRY_USERNAME: circleci CODECLIMATE_DOCKER_REGISTRY_EMAIL: ops@codeclimate.com -test: +dependencies: override: - docker build -t=$PRIVATE_REGISTRY/$CIRCLE_PROJECT_REPONAME:b$CIRCLE_BUILD_NUM . + - npm install + +test: + override: + - npm test deployment: registry: diff --git a/index.js b/index.js deleted file mode 100644 index 25e6682..0000000 --- a/index.js +++ /dev/null @@ -1,133 +0,0 @@ -var glob = require('glob'); -var exec = require('child_process').exec; -var fs = require('fs'); -var path = require('path'); - -module.exports = FixMe; -function FixMe() { } - -// Strings to scan for in source -var fixmeStrings = "'(FIXME|TODO|HACK|XXX|BUG)'"; - -var excludeExtensions = [".jpg", ".jpeg", ".png", ".gif"]; - -// Prints properly structured Issue data to STDOUT according to -// Code Climate Engine specification. -var printIssue = function(fileName, lineNum, matchedString){ - var issue = { - "type": "issue", - "check_name": matchedString, - "description": matchedString + " found", - "categories": ["Bug Risk"], - "location":{ - "path": fileName, - "lines": { - "begin": lineNum, - "end": lineNum - } - } - }; - - // Issues must be followed by a null byte - var issueString = JSON.stringify(issue)+"\0"; - console.log(issueString); -} - -var findFixmes = function(file){ - // Prepare the grep string for execution (uses BusyBox grep) - var grepString = "grep -inHwoE " + fixmeStrings + " " + file; - - // Execute grep with the FIXME patterns - exec(grepString, function (error, stdout, stderr) { - var results = stdout.toString(); - - if (results !== ""){ - // Parses grep output - var lines = results.split("\n"); - - lines.forEach(function(line, index, array){ - // grep spits out an extra line that we can ignore - if(index < (array.length-1)){ - // Grep output is colon delimited - var cols = line.split(":"); - - // Remove remnants of container paths for external display - var fileName = cols[0].split("/code/")[1]; - var lineNum = cols[1]; - var matchedString = cols[2]; - - if (matchedString !== undefined){ - printIssue(fileName, parseInt(lineNum), matchedString); - } - } - }) - } - }) -} - -// Returns an array of unique array values not included in the other provided array -var diff = function(a1, a2) { - var result = []; - - for (var i = 0; i < a1.length; i++) { - if (a2.indexOf(a1[i]) === -1) { - result.push(a1[i]); - } - - } - return result; -} - -// Returns all the file paths in the main directory that match the given pattern -var buildFiles = function(paths) { - var files = []; - - paths.forEach(function(path, i, a) { - var pattern = "/code/" + path + "**" - files.push.apply(files, glob.sync(pattern, {})); - }); - - return files; -} - -// Filters the directory paths out -var filterFiles = function(files) { - return files.filter(function(file) { - return !fs.lstatSync(file).isDirectory(); - }); - - return analysisFiles; -} - -// Returns file paths based on the exclude_paths values in config file -var buildFilesWithExclusions = function(exclusions) { - var allFiles = glob.sync("/code/**/**", {}); - var excludedFiles = buildFiles(exclusions); - - return diff(allFiles, excludedFiles); -} - -// Returns file paths based on the include_paths values in config file -var buildFilesWithInclusions = function(inclusions) { - return buildFiles(inclusions); -} - -FixMe.prototype.runEngine = function(){ - var analysisFiles = [] - - if (fs.existsSync("/config.json")) { - var engineConfig = JSON.parse(fs.readFileSync("/config.json")); - - if (engineConfig.hasOwnProperty("include_paths")) { - analysisFiles = buildFilesWithInclusions(engineConfig.include_paths); - } else if (engineConfig.hasOwnProperty("exclude_paths")) { - analysisFiles = buildFilesWithExclusions(engineConfig.exclude_paths); - } - } - - analysisFiles = filterFiles(analysisFiles); - // Execute main loop and find fixmes in valid files - analysisFiles.forEach(function(f, i, a){ - findFixmes(f); - }); -} diff --git a/lib/diff.js b/lib/diff.js new file mode 100644 index 0000000..3d5aecc --- /dev/null +++ b/lib/diff.js @@ -0,0 +1,13 @@ +// Returns an array of unique array values not included in the other provided array +function diff(a1, a2) { + var result = []; + + for (var i = 0; i < a1.length; i++) { + if (a2.indexOf(a1[i]) === -1) { + result.push(a1[i]); + } + } + return result; +} + +module.exports = diff; diff --git a/lib/file-builder.js b/lib/file-builder.js new file mode 100644 index 0000000..e0c05a4 --- /dev/null +++ b/lib/file-builder.js @@ -0,0 +1,41 @@ +var glob = require('glob'), + fs = require('fs'); + +// Returns file paths based on the include_paths values in config file +function withIncludes(include_paths) { + return buildFiles(include_paths); +} + +// Returns file paths based on the exclude_paths values in config file +function withExcludes(exclude_paths) { + var allFiles = glob.sync("/code/**/**", {}); + var excludedFiles = buildFiles(exclude_paths); + + return diff(allFiles, excludedFiles); +} + +// Returns all the file paths in the main directory that match the given pattern +function buildFiles(paths) { + var files = []; + + paths.forEach(function(path, i, a) { + var pattern = "/code/" + path + "**" + files.push.apply(files, glob.sync(pattern, {})); + }); + return files; +} + +// Filters the directory paths out +function filterFiles(paths) { + return paths.filter(function(file) { + return !fs.lstatSync(file).isDirectory(); + }); + return analysisFiles; +} + +module.exports = { + withIncludes: withIncludes, + withExcludes: withExcludes, + filterFiles: filterFiles +}; + diff --git a/lib/fix-me.js b/lib/fix-me.js new file mode 100644 index 0000000..65834bf --- /dev/null +++ b/lib/fix-me.js @@ -0,0 +1,86 @@ +var glob = require('glob'), + exec = require('child_process').exec, + fs = require('fs'), + path = require('path'), + diff = require('./diff'), + fileBuilder = require('./file-builder'); + +module.exports = FixMe; + +function FixMe() { } + +FixMe.prototype.runEngine = function(){ + var analysisFiles = [], + self = this; + + if (fs.existsSync('/config.json')) { + var engineConfig = JSON.parse(fs.readFileSync('/config.json')); + + if (engineConfig.hasOwnProperty('include_paths')) { + analysisFiles = fileBuilder.withIncludes(engineConfig.include_paths); + } else if (engineConfig.hasOwnProperty('exclude_paths')) { + analysisFiles = fileBuilder.withExcludes(engineConfig.exclude_paths); + } + } + + analysisFiles = fileBuilder.filterFiles(analysisFiles); + + analysisFiles.forEach(function(f, i, a){ + self.find(f); + }); +} + +FixMe.prototype.find = function(file){ + var fixmeStrings = "'(FIXME|TODO|HACK|XXX|BUG)'", + self = this; + + // Prepare the grep string for execution (uses BusyBox grep) + var grepString = "grep -inHwoE " + fixmeStrings + " " + file; + + // Execute grep with the FIXME patterns + exec(grepString, function (error, stdout, stderr) { + var results = stdout.toString(); + + if (results !== ""){ + // Parses grep output + var lines = results.split("\n"); + + lines.forEach(function(line, index, array){ + // grep spits out an extra line that we can ignore + if(index < (array.length-1)){ + // Grep output is colon delimited + var cols = line.split(":"); + + // Remove remnants of container paths for external display + var fileName = cols[0].split("/code/")[1]; + var lineNum = cols[1]; + var matchedString = cols[2]; + + if (matchedString !== undefined){ + self.printIssue(fileName, parseInt(lineNum), matchedString); + } + } + }) + } + }) +} + +FixMe.prototype.printIssue = function(fileName, lineNum, matchedString) { +// Prints properly structured Issue data to STDOUT according to Code Climate Engine specification. + var issue = { + "type": "issue", + "check_name": matchedString, + "description": matchedString + " found", + "categories": ["Bug Risk"], + "location":{ + "path": fileName, + "lines": { + "begin": lineNum, + "end": lineNum + } + } + }; + + var issueString = JSON.stringify(issue)+"\0"; + console.log(issueString); +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..59bfbf4 --- /dev/null +++ b/package.json @@ -0,0 +1,34 @@ +{ + "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", + "dependencies": { + "glob": "6.0.1" + }, + "devDependencies": { + "chai": "3.4.1", + "mocha": "2.3.3" + }, + "scripts": { + "test": "mocha" + }, + "files": [ + "LICENSE", + "README.md", + "bin", + "lib" + ], + "repository": { + "type": "git", + "url": "https://github.com/codeclimate/codeclimate-fixme.git" + }, + "keywords": [ + "fixme", + "codeclimate" + ], + "engines": { + "node": ">=0.10" + }, + "license": "MIT" +} diff --git a/test/diff.js b/test/diff.js new file mode 100644 index 0000000..1d6ec6f --- /dev/null +++ b/test/diff.js @@ -0,0 +1,14 @@ +var expect = require('chai').expect +var diff = require('../lib/diff.js') + +describe('diff(ary1, ary2)', function(){ + it('should return array of unique values from ary1 that are not present in ary2', function(){ + var aryOne = ['a', 'b', 'c', 1, 2, 3, 'foo/bar.js'], + aryTwo = ['d', 'b', 'c', 1, 2]; + + var uniqValues = diff(aryOne, aryTwo); + + expect(uniqValues).to.have.same.members(['a', 3, 'foo/bar.js']); + }); +}); + diff --git a/test/file-builder.js b/test/file-builder.js new file mode 100644 index 0000000..e766b64 --- /dev/null +++ b/test/file-builder.js @@ -0,0 +1,20 @@ +var expect = require('chai').expect +var fileBuilder = require('../lib/file-builder.js') + +describe("fileBuilder", function(){ + describe("#withIncludes(paths)", function(){ + xit("returns correct files", function(){ + // expect(); + }); + }); + describe("#withExcludes(paths)", function(){ + xit("returns correct files", function(){ + // expect(); + }); + }); + describe("#filterFiles(paths)", function(){ + xit("returns filters out directory paths", function(){ + // expect(); + }); + }); +}); diff --git a/test/fix-me.js b/test/fix-me.js new file mode 100644 index 0000000..46820fc --- /dev/null +++ b/test/fix-me.js @@ -0,0 +1,15 @@ +var expect = require('chai').expect +var fixMe = require('../lib/fix-me.js') + +describe("fixMe", function(){ + describe("#runEngine()", function(){ + xit("checks for /config.json", function(){ + // expect(); + }); + }); + describe("#find(file)", function(){ + xit("finds target fixme keywords", function(){ + // expect(); + }); + }); +}); diff --git a/test/test.js b/test/test.js deleted file mode 100644 index 4d741a4..0000000 --- a/test/test.js +++ /dev/null @@ -1,6 +0,0 @@ -// FIXME make this do something -function(){ - console.log("!"); -} - -// TODO make this do something too From 0eb3890388eba6fd1f8d35f1a1c43b1108d21042 Mon Sep 17 00:00:00 2001 From: ABaldwinHunter Date: Fri, 13 Nov 2015 13:07:22 -0500 Subject: [PATCH 09/44] Update dockerfile to copy package.json before running npm install We recently packaged codeclimate-fixme as a node module and so updated the Dockerfile to run simply `npm install`, grabbing the requisite dependencies from the package.json. However, we need to make sure that the package.json gets copied over first. This change fixes the Dockerfile and makes it look more like the flow in some of our other engines. E.g.: codeclimate-rubocop https://github.com/codeclimate/codeclimate-rubocop/blob/master/Dockerfile --- Dockerfile | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/Dockerfile b/Dockerfile index 281bff9..c7ff924 100644 --- a/Dockerfile +++ b/Dockerfile @@ -2,17 +2,18 @@ FROM node MAINTAINER Michael R. Bernstein -RUN useradd -u 9000 -r -s /bin/false app +WORKDIR /usr/src/app/ + +COPY engine.json / +COPY package.json /usr/src/app/ ENV NODE_ENV production RUN npm install -WORKDIR /code -COPY . /usr/src/app -COPY engine.json / - +RUN useradd -u 9000 -r -s /bin/false app USER app -VOLUME /code + +COPY . /usr/src/app CMD ["/usr/src/app/bin/fixme"] From 04423c45fabb9bb4ff2a0bc71bce250cb4eaa7c0 Mon Sep 17 00:00:00 2001 From: Will Fleming Date: Wed, 18 Nov 2015 20:27:56 -0500 Subject: [PATCH 10/44] quote filename shelling out to grep If a path has a space in it, the grep will fail unless we quote the filename. --- lib/fix-me.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/fix-me.js b/lib/fix-me.js index 65834bf..8b85231 100644 --- a/lib/fix-me.js +++ b/lib/fix-me.js @@ -35,7 +35,8 @@ FixMe.prototype.find = function(file){ self = this; // Prepare the grep string for execution (uses BusyBox grep) - var grepString = "grep -inHwoE " + fixmeStrings + " " + file; + var grepString = ["grep -inHwoE", fixmeStrings, '"' + file + '"'].join(" "); + // Execute grep with the FIXME patterns exec(grepString, function (error, stdout, stderr) { From 6ea4ea3f0de302f48fe375e216816256843b3485 Mon Sep 17 00:00:00 2001 From: ABaldwinHunter Date: Mon, 14 Dec 2015 17:38:40 -0500 Subject: [PATCH 11/44] Only search for all caps This change reduces false positives found by the fixme engine, such as `fixme: enabled: true` in a .codeclimate.yml --- lib/fix-me.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/fix-me.js b/lib/fix-me.js index 8b85231..cea3d96 100644 --- a/lib/fix-me.js +++ b/lib/fix-me.js @@ -35,7 +35,7 @@ FixMe.prototype.find = function(file){ self = this; // Prepare the grep string for execution (uses BusyBox grep) - var grepString = ["grep -inHwoE", fixmeStrings, '"' + file + '"'].join(" "); + var grepString = ["grep -nHwoE", fixmeStrings, '"' + file + '"'].join(" "); // Execute grep with the FIXME patterns From ee8d1cf27b2c9612cd8bc9aa17175dd7eb438167 Mon Sep 17 00:00:00 2001 From: ABaldwinHunter Date: Mon, 14 Dec 2015 17:40:22 -0500 Subject: [PATCH 12/44] Remove .codeclimate.yml from excludes There's no need to exclude this file now that only all caps trigger findings. --- .codeclimate.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.codeclimate.yml b/.codeclimate.yml index 96a5552..22eb9c9 100644 --- a/.codeclimate.yml +++ b/.codeclimate.yml @@ -11,4 +11,3 @@ exclude_paths: - "Dockerfile" - "bin/fixme" - "tests/**" -- ".codeclimate.yml" From 07f6e5e60884e745993622eb9ee3ed8714800800 Mon Sep 17 00:00:00 2001 From: patrick brisbin Date: Wed, 13 Jan 2016 16:17:30 -0500 Subject: [PATCH 13/44] Drop exclude_paths support, default configuration Defaulting to `include_paths: [./]` means it's possible to run docker run -v $PWD:/code $image to see raw output from running this engine, which is useful while developing. --- lib/file-builder.js | 9 --------- lib/fix-me.js | 12 +++++++----- test/file-builder.js | 5 ----- 3 files changed, 7 insertions(+), 19 deletions(-) diff --git a/lib/file-builder.js b/lib/file-builder.js index e0c05a4..bdf84bd 100644 --- a/lib/file-builder.js +++ b/lib/file-builder.js @@ -6,14 +6,6 @@ function withIncludes(include_paths) { return buildFiles(include_paths); } -// Returns file paths based on the exclude_paths values in config file -function withExcludes(exclude_paths) { - var allFiles = glob.sync("/code/**/**", {}); - var excludedFiles = buildFiles(exclude_paths); - - return diff(allFiles, excludedFiles); -} - // Returns all the file paths in the main directory that match the given pattern function buildFiles(paths) { var files = []; @@ -35,7 +27,6 @@ function filterFiles(paths) { module.exports = { withIncludes: withIncludes, - withExcludes: withExcludes, filterFiles: filterFiles }; diff --git a/lib/fix-me.js b/lib/fix-me.js index cea3d96..6f86197 100644 --- a/lib/fix-me.js +++ b/lib/fix-me.js @@ -11,18 +11,20 @@ function FixMe() { } FixMe.prototype.runEngine = function(){ var analysisFiles = [], + config = { + include_paths: ["./"] + }, self = this; if (fs.existsSync('/config.json')) { - var engineConfig = JSON.parse(fs.readFileSync('/config.json')); + var overrides = JSON.parse(fs.readFileSync('/config.json')); - if (engineConfig.hasOwnProperty('include_paths')) { - analysisFiles = fileBuilder.withIncludes(engineConfig.include_paths); - } else if (engineConfig.hasOwnProperty('exclude_paths')) { - analysisFiles = fileBuilder.withExcludes(engineConfig.exclude_paths); + for (var prop in overrides) { + config[prop] = overrides[prop]; } } + analysisFiles = fileBuilder.withIncludes(config.include_paths); analysisFiles = fileBuilder.filterFiles(analysisFiles); analysisFiles.forEach(function(f, i, a){ diff --git a/test/file-builder.js b/test/file-builder.js index e766b64..224069d 100644 --- a/test/file-builder.js +++ b/test/file-builder.js @@ -7,11 +7,6 @@ describe("fileBuilder", function(){ // expect(); }); }); - describe("#withExcludes(paths)", function(){ - xit("returns correct files", function(){ - // expect(); - }); - }); describe("#filterFiles(paths)", function(){ xit("returns filters out directory paths", function(){ // expect(); From 1d46c204085b9d2e055952d0ce43e87a4631a1cb Mon Sep 17 00:00:00 2001 From: patrick brisbin Date: Wed, 13 Jan 2016 16:24:14 -0500 Subject: [PATCH 14/44] Allow configurability of the FIXME strings --- README.md | 17 +++++++++++++++++ lib/fix-me.js | 9 +++++---- 2 files changed, 22 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 7516d87..e1ee050 100644 --- a/README.md +++ b/README.md @@ -17,6 +17,23 @@ These strings are things you should fix now, not later. 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 + 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. diff --git a/lib/fix-me.js b/lib/fix-me.js index 6f86197..e8cd911 100644 --- a/lib/fix-me.js +++ b/lib/fix-me.js @@ -12,7 +12,8 @@ function FixMe() { } FixMe.prototype.runEngine = function(){ var analysisFiles = [], config = { - include_paths: ["./"] + include_paths: ["./"], + strings: ["FIXME", "TODO", "HACK", "XXX", "BUG"] }, self = this; @@ -28,12 +29,12 @@ FixMe.prototype.runEngine = function(){ analysisFiles = fileBuilder.filterFiles(analysisFiles); analysisFiles.forEach(function(f, i, a){ - self.find(f); + self.find(f, config.strings); }); } -FixMe.prototype.find = function(file){ - var fixmeStrings = "'(FIXME|TODO|HACK|XXX|BUG)'", +FixMe.prototype.find = function(file, strings){ + var fixmeStrings = "'(" + strings.join("|") + ")'", self = this; // Prepare the grep string for execution (uses BusyBox grep) From 2d9e61b33ac842f678973ff4d17dd2da1444dbe8 Mon Sep 17 00:00:00 2001 From: ABaldwinHunter Date: Mon, 1 Feb 2016 14:08:25 -0500 Subject: [PATCH 15/44] BUGFIX - make fixme strings customizable This fixes a bug in which the custom search strings weren't getting picked up by the config parser. --- README.md | 7 ++++--- lib/fix-me.js | 10 +++++++--- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index e1ee050..4d1dd04 100644 --- a/README.md +++ b/README.md @@ -26,9 +26,10 @@ You can specify what strings to match by adding a `strings` key in your engines: fixme: enabled: true - strings: - - FIXME - - CUSTOM + config: + strings: + - FIXME + - CUSTOM ``` **NOTE**: values specified here *override* the defaults, they are not diff --git a/lib/fix-me.js b/lib/fix-me.js index e8cd911..98e35c3 100644 --- a/lib/fix-me.js +++ b/lib/fix-me.js @@ -18,10 +18,14 @@ FixMe.prototype.runEngine = function(){ self = this; if (fs.existsSync('/config.json')) { - var overrides = JSON.parse(fs.readFileSync('/config.json')); + var userConfig = JSON.parse(fs.readFileSync('/config.json')); - for (var prop in overrides) { - config[prop] = overrides[prop]; + config.include_paths = userConfig.include_paths; + + if (userConfig.config) { + for (var prop in userConfig.config) { + config[prop] = userConfig.config[prop]; + } } } From 1c68053e6f1e9871d336593c56072f14a9cfc214 Mon Sep 17 00:00:00 2001 From: ABaldwinHunter Date: Thu, 4 Feb 2016 14:59:07 -0500 Subject: [PATCH 16/44] Fix bug in how we determine file name Don't take first element after splitting on '/code/', because it creates a bug when `/code` is included in source code file path. Add spec. --- .codeclimate.yml | 2 +- lib/fix-me.js | 9 +++++--- test/fix-me.js | 35 ++++++++++++++++++++++++----- test/fixtures/code/src/code/test.js | 6 +++++ 4 files changed, 43 insertions(+), 9 deletions(-) create mode 100644 test/fixtures/code/src/code/test.js diff --git a/.codeclimate.yml b/.codeclimate.yml index 22eb9c9..438d8cf 100644 --- a/.codeclimate.yml +++ b/.codeclimate.yml @@ -10,4 +10,4 @@ exclude_paths: - "**/*.md" - "Dockerfile" - "bin/fixme" -- "tests/**" +- "test/**" diff --git a/lib/fix-me.js b/lib/fix-me.js index 98e35c3..6af3c73 100644 --- a/lib/fix-me.js +++ b/lib/fix-me.js @@ -44,7 +44,6 @@ FixMe.prototype.find = function(file, strings){ // Prepare the grep string for execution (uses BusyBox grep) var grepString = ["grep -nHwoE", fixmeStrings, '"' + file + '"'].join(" "); - // Execute grep with the FIXME patterns exec(grepString, function (error, stdout, stderr) { var results = stdout.toString(); @@ -55,12 +54,12 @@ FixMe.prototype.find = function(file, strings){ lines.forEach(function(line, index, array){ // grep spits out an extra line that we can ignore - if(index < (array.length-1)){ + if (index < (array.length-1)) { // Grep output is colon delimited var cols = line.split(":"); // Remove remnants of container paths for external display - var fileName = cols[0].split("/code/")[1]; + var fileName = self.formatPath(cols[0]); var lineNum = cols[1]; var matchedString = cols[2]; @@ -92,3 +91,7 @@ FixMe.prototype.printIssue = function(fileName, lineNum, matchedString) { var issueString = JSON.stringify(issue)+"\0"; console.log(issueString); } + +FixMe.prototype.formatPath = function(path) { + return path.replace(/^\/code\//, ''); +} diff --git a/test/fix-me.js b/test/fix-me.js index 46820fc..cfa7724 100644 --- a/test/fix-me.js +++ b/test/fix-me.js @@ -1,5 +1,7 @@ -var expect = require('chai').expect -var fixMe = require('../lib/fix-me.js') +var expect = require('chai').expect, + intercept = require("intercept-stdout"), + fixMe = require('../lib/fix-me.js'), + engine = new fixMe; describe("fixMe", function(){ describe("#runEngine()", function(){ @@ -7,9 +9,32 @@ describe("fixMe", function(){ // expect(); }); }); - describe("#find(file)", function(){ - xit("finds target fixme keywords", function(){ - // expect(); + + describe('#find(file)', function(){ + it('finds and correctly prints TODO issues', function(done){ + var capturedText = "", + unhookIntercept = intercept(function(txt) { + capturedText += txt; + }); + + engine.find('test/fixtures/code/src/code/test.js', ["FIXME", "TODO", "HACK", "XXX", "BUG"]); + + // to capture standard output from engine, wait. + setTimeout(function() { + unhookIntercept(); + + expect(capturedText).to.eq('{"type":"issue","check_name":"TODO","description":"TODO found","categories":["Bug Risk"],"location":{"path":"test/fixtures/code/src/code/test.js","lines":{"begin":5,"end":5}}}\0\n'); + done(); + }, 10); + }); + }); + + describe('#formatPath(path)', function(){ + it('returns correct filename for files with /code in them', function(){ + var path = '/code/src/javascripts/code/test.js', + formatted = engine.formatPath(path); + + expect(formatted).to.eq('src/javascripts/code/test.js'); }); }); }); diff --git a/test/fixtures/code/src/code/test.js b/test/fixtures/code/src/code/test.js new file mode 100644 index 0000000..94634a2 --- /dev/null +++ b/test/fixtures/code/src/code/test.js @@ -0,0 +1,6 @@ + +function() { + console.log("hello world!"); +} +// TODO +// SUP From f864db097253f4ee7b4e1210018fd79f61bf3c57 Mon Sep 17 00:00:00 2001 From: ABaldwinHunter Date: Fri, 5 Feb 2016 11:03:42 -0500 Subject: [PATCH 17/44] Add intercept-stdout to package.json for testing --- package.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index 59bfbf4..5cd4cb1 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,8 @@ }, "devDependencies": { "chai": "3.4.1", - "mocha": "2.3.3" + "mocha": "2.3.3", + "intercept-stdout": "0.1.2" }, "scripts": { "test": "mocha" From e5a8a0701a4ab0bf7976b3fca8e4c4f32b9fc8ba Mon Sep 17 00:00:00 2001 From: Will Fleming Date: Tue, 9 Feb 2016 12:14:23 -0500 Subject: [PATCH 18/44] add Makefile --- Makefile | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 Makefile diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..739d039 --- /dev/null +++ b/Makefile @@ -0,0 +1,9 @@ +.PHONY: image test + +IMAGE_NAME ?= codeclimate/codeclimate-fixme + +image: + docker build --rm -t $(IMAGE_NAME) . + +test: image + docker run --rm $(IMAGE_NAME) npm test From 7d2b8b5124793ba1ff22963e483903e8e72e47a9 Mon Sep 17 00:00:00 2001 From: Will Fleming Date: Tue, 9 Feb 2016 12:19:15 -0500 Subject: [PATCH 19/44] Dockerfile: tag base image, add maintainer email Tagging a base image is good practice regardless. The slim variant also shaves ~400MB off our final image. And having a maintainer email is nice. --- Dockerfile | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/Dockerfile b/Dockerfile index c7ff924..8a85199 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,6 +1,5 @@ -FROM node - -MAINTAINER Michael R. Bernstein +FROM node:5.5-slim +MAINTAINER Michael R. Bernstein WORKDIR /usr/src/app/ From 14615d161a9d283cf9796855b86a188192876cce Mon Sep 17 00:00:00 2001 From: Will Fleming Date: Tue, 9 Feb 2016 12:21:07 -0500 Subject: [PATCH 20/44] Update circle.yml to use Makefile Use the makefile to build & tag the image, as well as run tests in it. --- circle.yml | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/circle.yml b/circle.yml index 63d9dec..5d2b724 100644 --- a/circle.yml +++ b/circle.yml @@ -9,13 +9,11 @@ machine: CODECLIMATE_DOCKER_REGISTRY_EMAIL: ops@codeclimate.com dependencies: - override: - - docker build -t=$PRIVATE_REGISTRY/$CIRCLE_PROJECT_REPONAME:b$CIRCLE_BUILD_NUM . - - npm install + override: #no-op test: override: - - npm test + - IMAGE_NAME="$PRIVATE_REGISTRY/$CIRCLE_PROJECT_REPONAME:b$CIRCLE_BUILD_NUM" make test deployment: registry: From e2e02f38e538c8216c244b57fa13a4d1574a339a Mon Sep 17 00:00:00 2001 From: ABaldwinHunter Date: Thu, 11 Feb 2016 12:12:12 -0500 Subject: [PATCH 21/44] Update readme Include `XXX` in list of target words and call out that match is case sensitive. Update description of tool behavior to clarify that strings can be found outside of comments as well. --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 4d1dd04..5621154 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,12 @@ # Code Climate FIXME Engine -`codeclimate-fixme` is a Code Climate engine that finds comments in your code which match the following strings: +`codeclimate-fixme` is a Code Climate engine that performs a case-sensitive search for the following strings in your project: * `TODO` * `FIXME` * `HACK` * `BUG` +* `XXX` These strings are things you should fix now, not later. From 86597d25c2f67eef5efcb7c17cbe4855c2864f10 Mon Sep 17 00:00:00 2001 From: Gordon Diggs Date: Mon, 22 Feb 2016 09:45:19 -0500 Subject: [PATCH 22/44] Remove CC registry --- circle.yml | 6 ------ 1 file changed, 6 deletions(-) diff --git a/circle.yml b/circle.yml index 5d2b724..64e9fce 100644 --- a/circle.yml +++ b/circle.yml @@ -4,9 +4,6 @@ machine: environment: CLOUDSDK_CORE_DISABLE_PROMPTS: 1 PRIVATE_REGISTRY: us.gcr.io/code_climate - CODECLIMATE_DOCKER_REGISTRY_HOSTNAME: registry.codeclimate.net - CODECLIMATE_DOCKER_REGISTRY_USERNAME: circleci - CODECLIMATE_DOCKER_REGISTRY_EMAIL: ops@codeclimate.com dependencies: override: #no-op @@ -25,9 +22,6 @@ deployment: - gcloud auth activate-service-account --key-file /tmp/gcloud_key.json - gcloud docker -a - docker push $PRIVATE_REGISTRY/$CIRCLE_PROJECT_REPONAME:b$CIRCLE_BUILD_NUM - - docker login --username=$CODECLIMATE_DOCKER_REGISTRY_USERNAME --password=$CODECLIMATE_DOCKER_REGISTRY_PASSWORD --email=$CODECLIMATE_DOCKER_REGISTRY_EMAIL $CODECLIMATE_DOCKER_REGISTRY_HOSTNAME - - docker tag $PRIVATE_REGISTRY/$CIRCLE_PROJECT_REPONAME:b$CIRCLE_BUILD_NUM $CODECLIMATE_DOCKER_REGISTRY_HOSTNAME/$CIRCLE_PROJECT_USERNAME/$CIRCLE_PROJECT_REPONAME:b$CIRCLE_BUILD_NUM - - docker push $CODECLIMATE_DOCKER_REGISTRY_HOSTNAME/$CIRCLE_PROJECT_USERNAME/$CIRCLE_PROJECT_REPONAME:b$CIRCLE_BUILD_NUM notify: webhooks: From 19352fd10118a009e1e30cb4fb6cfbb6fe441975 Mon Sep 17 00:00:00 2001 From: John Pignata Date: Fri, 11 Mar 2016 16:28:04 -0500 Subject: [PATCH 23/44] Invoke grep only once per run Previously we were running grep for each path passed to us in the workspace. This led to situations where we had tens of thousands of zombie processes waiting for the parent to exit on large repos. Instead, invoke grep recursively for all files in the workspace a single time. * Use spawn instead of exec to stream results back --- lib/fix-me.js | 23 +++++++++-------------- 1 file changed, 9 insertions(+), 14 deletions(-) diff --git a/lib/fix-me.js b/lib/fix-me.js index 6af3c73..a5453b0 100644 --- a/lib/fix-me.js +++ b/lib/fix-me.js @@ -1,5 +1,5 @@ var glob = require('glob'), - exec = require('child_process').exec, + spawn = require('child_process').spawn, fs = require('fs'), path = require('path'), diff = require('./diff'), @@ -32,21 +32,16 @@ FixMe.prototype.runEngine = function(){ analysisFiles = fileBuilder.withIncludes(config.include_paths); analysisFiles = fileBuilder.filterFiles(analysisFiles); - analysisFiles.forEach(function(f, i, a){ - self.find(f, config.strings); - }); + self.find(analysisFiles, config.strings); } -FixMe.prototype.find = function(file, strings){ - var fixmeStrings = "'(" + strings.join("|") + ")'", - self = this; - - // Prepare the grep string for execution (uses BusyBox grep) - var grepString = ["grep -nHwoE", fixmeStrings, '"' + file + '"'].join(" "); +FixMe.prototype.find = function(files, strings){ + var fixmeStrings = '(' + strings.join('|') + ')'; + var grep = spawn('grep', ['-nHwoEr', fixmeStrings].concat(files)); + var self = this; - // Execute grep with the FIXME patterns - exec(grepString, function (error, stdout, stderr) { - var results = stdout.toString(); + grep.stdout.on('data', function (data) { + var results = data.toString(); if (results !== ""){ // Parses grep output @@ -69,7 +64,7 @@ FixMe.prototype.find = function(file, strings){ } }) } - }) + }); } FixMe.prototype.printIssue = function(fileName, lineNum, matchedString) { From f7f966d2d88a47596d6f8273d6aa8578ebd658a4 Mon Sep 17 00:00:00 2001 From: John Pignata Date: Fri, 11 Mar 2016 18:48:40 -0500 Subject: [PATCH 24/44] Don't try to parse output until finished I switched to spawn from exec in order to try to stream issues as they were reported by grep but node will pass chunks of output that may not terminate with a line break causing a line to straddle two chunks. For now buffer all of the data and output issues on exit. In another PR I'll clean this up. --- lib/fix-me.js | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/lib/fix-me.js b/lib/fix-me.js index a5453b0..3d453db 100644 --- a/lib/fix-me.js +++ b/lib/fix-me.js @@ -38,14 +38,17 @@ FixMe.prototype.runEngine = function(){ FixMe.prototype.find = function(files, strings){ var fixmeStrings = '(' + strings.join('|') + ')'; var grep = spawn('grep', ['-nHwoEr', fixmeStrings].concat(files)); + var output = ""; var self = this; - grep.stdout.on('data', function (data) { - var results = data.toString(); + grep.stdout.on('data', function(data) { + output += data.toString(); + }); - if (results !== ""){ + grep.on('exit', function() { + if (output !== ""){ // Parses grep output - var lines = results.split("\n"); + var lines = output.split("\n"); lines.forEach(function(line, index, array){ // grep spits out an extra line that we can ignore From 4ec360b259cd6344b9a9f2bb3117808619b35128 Mon Sep 17 00:00:00 2001 From: John Pignata Date: Fri, 11 Mar 2016 19:37:24 -0500 Subject: [PATCH 25/44] Fix test --- lib/fix-me.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/fix-me.js b/lib/fix-me.js index 3d453db..55bdcc8 100644 --- a/lib/fix-me.js +++ b/lib/fix-me.js @@ -45,7 +45,7 @@ FixMe.prototype.find = function(files, strings){ output += data.toString(); }); - grep.on('exit', function() { + grep.stdout.on('close', function() { if (output !== ""){ // Parses grep output var lines = output.split("\n"); From 489f67af86fb504a8ba5b9bf032b19d7f6c922d6 Mon Sep 17 00:00:00 2001 From: John Pignata Date: Fri, 11 Mar 2016 19:41:23 -0500 Subject: [PATCH 26/44] Pass bare workspace Instead of exploding a workspace into its individual files, pass the bare workspace provided to us by codeclimate. * Remove unused array diff function --- lib/diff.js | 13 ------------- lib/file-builder.js | 32 -------------------------------- lib/fix-me.js | 15 +++++---------- test/diff.js | 14 -------------- test/file-builder.js | 15 --------------- 5 files changed, 5 insertions(+), 84 deletions(-) delete mode 100644 lib/diff.js delete mode 100644 lib/file-builder.js delete mode 100644 test/diff.js delete mode 100644 test/file-builder.js diff --git a/lib/diff.js b/lib/diff.js deleted file mode 100644 index 3d5aecc..0000000 --- a/lib/diff.js +++ /dev/null @@ -1,13 +0,0 @@ -// Returns an array of unique array values not included in the other provided array -function diff(a1, a2) { - var result = []; - - for (var i = 0; i < a1.length; i++) { - if (a2.indexOf(a1[i]) === -1) { - result.push(a1[i]); - } - } - return result; -} - -module.exports = diff; diff --git a/lib/file-builder.js b/lib/file-builder.js deleted file mode 100644 index bdf84bd..0000000 --- a/lib/file-builder.js +++ /dev/null @@ -1,32 +0,0 @@ -var glob = require('glob'), - fs = require('fs'); - -// Returns file paths based on the include_paths values in config file -function withIncludes(include_paths) { - return buildFiles(include_paths); -} - -// Returns all the file paths in the main directory that match the given pattern -function buildFiles(paths) { - var files = []; - - paths.forEach(function(path, i, a) { - var pattern = "/code/" + path + "**" - files.push.apply(files, glob.sync(pattern, {})); - }); - return files; -} - -// Filters the directory paths out -function filterFiles(paths) { - return paths.filter(function(file) { - return !fs.lstatSync(file).isDirectory(); - }); - return analysisFiles; -} - -module.exports = { - withIncludes: withIncludes, - filterFiles: filterFiles -}; - diff --git a/lib/fix-me.js b/lib/fix-me.js index 55bdcc8..d188699 100644 --- a/lib/fix-me.js +++ b/lib/fix-me.js @@ -1,9 +1,7 @@ -var glob = require('glob'), - spawn = require('child_process').spawn, - fs = require('fs'), - path = require('path'), - diff = require('./diff'), - fileBuilder = require('./file-builder'); +var glob = require('glob'); +var spawn = require('child_process').spawn; +var fs = require('fs'); +var path = require('path'); module.exports = FixMe; @@ -29,10 +27,7 @@ FixMe.prototype.runEngine = function(){ } } - analysisFiles = fileBuilder.withIncludes(config.include_paths); - analysisFiles = fileBuilder.filterFiles(analysisFiles); - - self.find(analysisFiles, config.strings); + self.find(config.include_paths, config.strings); } FixMe.prototype.find = function(files, strings){ diff --git a/test/diff.js b/test/diff.js deleted file mode 100644 index 1d6ec6f..0000000 --- a/test/diff.js +++ /dev/null @@ -1,14 +0,0 @@ -var expect = require('chai').expect -var diff = require('../lib/diff.js') - -describe('diff(ary1, ary2)', function(){ - it('should return array of unique values from ary1 that are not present in ary2', function(){ - var aryOne = ['a', 'b', 'c', 1, 2, 3, 'foo/bar.js'], - aryTwo = ['d', 'b', 'c', 1, 2]; - - var uniqValues = diff(aryOne, aryTwo); - - expect(uniqValues).to.have.same.members(['a', 3, 'foo/bar.js']); - }); -}); - diff --git a/test/file-builder.js b/test/file-builder.js deleted file mode 100644 index 224069d..0000000 --- a/test/file-builder.js +++ /dev/null @@ -1,15 +0,0 @@ -var expect = require('chai').expect -var fileBuilder = require('../lib/file-builder.js') - -describe("fileBuilder", function(){ - describe("#withIncludes(paths)", function(){ - xit("returns correct files", function(){ - // expect(); - }); - }); - describe("#filterFiles(paths)", function(){ - xit("returns filters out directory paths", function(){ - // expect(); - }); - }); -}); From 000a1d09d11f38c5f08e686b27617958f1df6b99 Mon Sep 17 00:00:00 2001 From: John Pignata Date: Fri, 11 Mar 2016 19:48:32 -0500 Subject: [PATCH 27/44] Use readline to stream issues Since data can be returned in chunks, feed the stream to readline so it can buffer each chunk and call our callback on each line to return the issue. * Inline formatPath --- lib/fix-me.js | 68 ++++++++++++++++---------------------------------- test/fix-me.js | 9 ------- 2 files changed, 21 insertions(+), 56 deletions(-) diff --git a/lib/fix-me.js b/lib/fix-me.js index d188699..9973665 100644 --- a/lib/fix-me.js +++ b/lib/fix-me.js @@ -2,6 +2,7 @@ var glob = require('glob'); var spawn = require('child_process').spawn; var fs = require('fs'); var path = require('path'); +var readline = require('readline'); module.exports = FixMe; @@ -36,55 +37,28 @@ FixMe.prototype.find = function(files, strings){ var output = ""; var self = this; - grep.stdout.on('data', function(data) { - output += data.toString(); - }); - - grep.stdout.on('close', function() { - if (output !== ""){ - // Parses grep output - var lines = output.split("\n"); - - lines.forEach(function(line, index, array){ - // grep spits out an extra line that we can ignore - if (index < (array.length-1)) { - // Grep output is colon delimited - var cols = line.split(":"); - - // Remove remnants of container paths for external display - var fileName = self.formatPath(cols[0]); - var lineNum = cols[1]; - var matchedString = cols[2]; - - if (matchedString !== undefined){ - self.printIssue(fileName, parseInt(lineNum), matchedString); + readline.createInterface({ input: grep.stdout }).on('line', function(line) { + var cols = line.split(":"); + var fileName = cols[0].replace(/^\/code\//, ''); + var lineNum = parseInt(cols[1]); + var matchedString = cols[2]; + + if (matchedString !== undefined){ + var issue = JSON.stringify({ + "type": "issue", + "check_name": matchedString, + "description": matchedString + " found", + "categories": ["Bug Risk"], + "location":{ + "path": fileName, + "lines": { + "begin": lineNum, + "end": lineNum } } - }) - } - }); -} + }); -FixMe.prototype.printIssue = function(fileName, lineNum, matchedString) { -// Prints properly structured Issue data to STDOUT according to Code Climate Engine specification. - var issue = { - "type": "issue", - "check_name": matchedString, - "description": matchedString + " found", - "categories": ["Bug Risk"], - "location":{ - "path": fileName, - "lines": { - "begin": lineNum, - "end": lineNum - } + console.log(issue+'\0'); } - }; - - var issueString = JSON.stringify(issue)+"\0"; - console.log(issueString); -} - -FixMe.prototype.formatPath = function(path) { - return path.replace(/^\/code\//, ''); + }); } diff --git a/test/fix-me.js b/test/fix-me.js index cfa7724..f8e1f90 100644 --- a/test/fix-me.js +++ b/test/fix-me.js @@ -28,13 +28,4 @@ describe("fixMe", function(){ }, 10); }); }); - - describe('#formatPath(path)', function(){ - it('returns correct filename for files with /code in them', function(){ - var path = '/code/src/javascripts/code/test.js', - formatted = engine.formatPath(path); - - expect(formatted).to.eq('src/javascripts/code/test.js'); - }); - }); }); From ea526d5664ffc969f0aa4343e006acda8f95197f Mon Sep 17 00:00:00 2001 From: John Pignata Date: Fri, 11 Mar 2016 19:52:04 -0500 Subject: [PATCH 28/44] Consistency fixes * Fix spacing * Use single quotes * Remove unneeded self references --- lib/fix-me.js | 52 +++++++++++++++++++++++++-------------------------- 1 file changed, 25 insertions(+), 27 deletions(-) diff --git a/lib/fix-me.js b/lib/fix-me.js index 9973665..e4b1cd3 100644 --- a/lib/fix-me.js +++ b/lib/fix-me.js @@ -1,20 +1,17 @@ -var glob = require('glob'); -var spawn = require('child_process').spawn; var fs = require('fs'); +var glob = require('glob'); var path = require('path'); var readline = require('readline'); +var spawn = require('child_process').spawn; -module.exports = FixMe; - -function FixMe() { } +function FixMe() {} -FixMe.prototype.runEngine = function(){ - var analysisFiles = [], - config = { - include_paths: ["./"], - strings: ["FIXME", "TODO", "HACK", "XXX", "BUG"] - }, - self = this; +FixMe.prototype.runEngine = function() { + var analysisFiles = []; + var config = { + include_paths: ['./'], + strings: ['FIXME', 'TODO', 'HACK', 'XXX', 'BUG'] + }; if (fs.existsSync('/config.json')) { var userConfig = JSON.parse(fs.readFileSync('/config.json')); @@ -28,37 +25,38 @@ FixMe.prototype.runEngine = function(){ } } - self.find(config.include_paths, config.strings); + this.find(config.include_paths, config.strings); } -FixMe.prototype.find = function(files, strings){ +FixMe.prototype.find = function(files, strings) { var fixmeStrings = '(' + strings.join('|') + ')'; var grep = spawn('grep', ['-nHwoEr', fixmeStrings].concat(files)); - var output = ""; - var self = this; + var output = ''; readline.createInterface({ input: grep.stdout }).on('line', function(line) { - var cols = line.split(":"); + var cols = line.split(':'); var fileName = cols[0].replace(/^\/code\//, ''); var lineNum = parseInt(cols[1]); var matchedString = cols[2]; if (matchedString !== undefined){ var issue = JSON.stringify({ - "type": "issue", - "check_name": matchedString, - "description": matchedString + " found", - "categories": ["Bug Risk"], - "location":{ - "path": fileName, - "lines": { - "begin": lineNum, - "end": lineNum + 'type': 'issue', + 'check_name': matchedString, + 'description': matchedString + ' found', + 'categories': ['Bug Risk'], + 'location':{ + 'path': fileName, + 'lines': { + 'begin': lineNum, + 'end': lineNum } } }); - console.log(issue+'\0'); + console.log(issue + '\0'); } }); } + +module.exports = FixMe; From b9a2797105ffacb3a13ddfeab1a08dd5775b607f Mon Sep 17 00:00:00 2001 From: John Pignata Date: Fri, 11 Mar 2016 20:17:33 -0500 Subject: [PATCH 29/44] Explode single letter arguments into option keywords --- lib/fix-me.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/fix-me.js b/lib/fix-me.js index e4b1cd3..5214c51 100644 --- a/lib/fix-me.js +++ b/lib/fix-me.js @@ -30,8 +30,10 @@ FixMe.prototype.runEngine = function() { FixMe.prototype.find = function(files, strings) { var fixmeStrings = '(' + strings.join('|') + ')'; - var grep = spawn('grep', ['-nHwoEr', fixmeStrings].concat(files)); - var output = ''; + var args = ['--line-number', '--with-filename', '--word-regexp', + '--only-matching', '--extended-regexp', '--recursive', + '--binary-files=without-match', fixmeStrings]; + var grep = spawn('grep', args.concat(files)); readline.createInterface({ input: grep.stdout }).on('line', function(line) { var cols = line.split(':'); From 21dd6073c9e43d801d4fa77895769ab57d5a71eb Mon Sep 17 00:00:00 2001 From: John Pignata Date: Fri, 11 Mar 2016 20:18:14 -0500 Subject: [PATCH 30/44] Remove unused local --- lib/fix-me.js | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/fix-me.js b/lib/fix-me.js index 5214c51..6e6b07c 100644 --- a/lib/fix-me.js +++ b/lib/fix-me.js @@ -7,7 +7,6 @@ var spawn = require('child_process').spawn; function FixMe() {} FixMe.prototype.runEngine = function() { - var analysisFiles = []; var config = { include_paths: ['./'], strings: ['FIXME', 'TODO', 'HACK', 'XXX', 'BUG'] From fa76fe98f620fd990ec799eb982329b88ed65019 Mon Sep 17 00:00:00 2001 From: John Pignata Date: Fri, 11 Mar 2016 20:29:40 -0500 Subject: [PATCH 31/44] Fix Dockerfile The Code Climate mandates the WORKDIR set to /code. --- Dockerfile | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Dockerfile b/Dockerfile index 8a85199..a516479 100644 --- a/Dockerfile +++ b/Dockerfile @@ -15,4 +15,7 @@ USER app COPY . /usr/src/app +VOLUME /code +WORKDIR /code + CMD ["/usr/src/app/bin/fixme"] From 33f79a798344802f562d52df1caeca2e9e707122 Mon Sep 17 00:00:00 2001 From: John Pignata Date: Fri, 11 Mar 2016 20:30:52 -0500 Subject: [PATCH 32/44] Remove unnecessary modules --- lib/fix-me.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/lib/fix-me.js b/lib/fix-me.js index 6e6b07c..1cf58a6 100644 --- a/lib/fix-me.js +++ b/lib/fix-me.js @@ -1,6 +1,4 @@ var fs = require('fs'); -var glob = require('glob'); -var path = require('path'); var readline = require('readline'); var spawn = require('child_process').spawn; From fa59b5cd385d57636ca4b5d04293d7897543d6c4 Mon Sep 17 00:00:00 2001 From: John Pignata Date: Fri, 11 Mar 2016 20:40:57 -0500 Subject: [PATCH 33/44] Fix Makefile With a properly set WORKDIR, we need to ensure that we chdir into the right directory before running the tests. --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 739d039..3dee05f 100644 --- a/Makefile +++ b/Makefile @@ -6,4 +6,4 @@ image: docker build --rm -t $(IMAGE_NAME) . test: image - docker run --rm $(IMAGE_NAME) npm test + docker run --rm $(IMAGE_NAME) sh -c "cd /usr/src/app && npm test" From 1457955bb40fdadbf4e2f7003f76666aa71b28df Mon Sep 17 00:00:00 2001 From: John Pignata Date: Fri, 11 Mar 2016 23:57:43 -0500 Subject: [PATCH 34/44] Improve test coverage * Move configuration parsing out of engine and into binstub * Add .eslintignore from inferred configuration * Add .dockerignore * Update .estlintrc to newer defaults * Small changes for clarity --- .codeclimate.yml | 6 +- .dockerignore | 2 + .eslintignore | 1 + .eslintrc | 374 +++++++++++++--------------- Dockerfile | 2 - Makefile | 2 +- bin/fixme | 8 +- circle.yml | 3 +- lib/fix-me.js | 98 ++++---- package.json | 6 +- test/fix-me.js | 170 +++++++++++-- test/fixtures/binary.out | Bin 0 -> 8488 bytes test/fixtures/case-sensitivity.js | 5 + test/fixtures/code/src/code/test.js | 6 - test/fixtures/file.js | 9 + test/fixtures/whole-words.js | 9 + 16 files changed, 407 insertions(+), 294 deletions(-) create mode 100644 .dockerignore create mode 100644 .eslintignore create mode 100644 test/fixtures/binary.out create mode 100644 test/fixtures/case-sensitivity.js delete mode 100644 test/fixtures/code/src/code/test.js create mode 100644 test/fixtures/file.js create mode 100644 test/fixtures/whole-words.js diff --git a/.codeclimate.yml b/.codeclimate.yml index 438d8cf..dc20aff 100644 --- a/.codeclimate.yml +++ b/.codeclimate.yml @@ -7,7 +7,5 @@ ratings: paths: - "**.js" exclude_paths: -- "**/*.md" -- "Dockerfile" -- "bin/fixme" -- "test/**" +- "test" +- "node_modules" diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..85dcc16 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,2 @@ +.git +node_modules diff --git a/.eslintignore b/.eslintignore new file mode 100644 index 0000000..96212a3 --- /dev/null +++ b/.eslintignore @@ -0,0 +1 @@ +**/*{.,-}min.js diff --git a/.eslintrc b/.eslintrc index 233f4e7..9221332 100644 --- a/.eslintrc +++ b/.eslintrc @@ -1,253 +1,213 @@ -ecmaFeatures: {} +ecmaFeatures: + modules: true + jsx: true + +env: + amd: true + browser: true + es6: true + jquery: true + node: true + +# http://eslint.org/docs/rules/ rules: - no-alert: 0 - no-array-constructor: 0 - no-bitwise: 0 - no-caller: 0 - no-catch-shadow: 0 - no-class-assign: 0 + # Possible Errors + comma-dangle: 0 no-cond-assign: 2 - no-console: 2 - no-const-assign: 0 + no-console: 0 no-constant-condition: 2 - no-continue: 0 no-control-regex: 2 no-debugger: 2 - no-delete-var: 2 - no-div-regex: 0 - no-dupe-keys: 2 no-dupe-args: 2 + no-dupe-keys: 2 no-duplicate-case: 2 - no-else-return: 0 no-empty: 2 no-empty-character-class: 2 - no-empty-label: 0 - no-eq-null: 0 - no-eval: 0 no-ex-assign: 2 - no-extend-native: 0 - no-extra-bind: 0 no-extra-boolean-cast: 2 no-extra-parens: 0 no-extra-semi: 2 + no-func-assign: 2 + no-inner-declarations: [2, functions] + no-invalid-regexp: 2 + no-irregular-whitespace: 2 + no-negated-in-lhs: 2 + no-obj-calls: 2 + no-regex-spaces: 2 + no-sparse-arrays: 2 + no-unexpected-multiline: 2 + no-unreachable: 2 + use-isnan: 2 + valid-jsdoc: 0 + valid-typeof: 2 + + # Best Practices + accessor-pairs: 2 + block-scoped-var: 0 + complexity: [2, 6] + consistent-return: 0 + curly: 0 + default-case: 0 + dot-location: 0 + dot-notation: 0 + eqeqeq: 2 + guard-for-in: 2 + no-alert: 2 + no-caller: 2 + no-case-declarations: 2 + no-div-regex: 2 + no-else-return: 0 + no-empty-label: 2 + no-empty-pattern: 2 + no-eq-null: 2 + no-eval: 2 + no-extend-native: 2 + no-extra-bind: 2 no-fallthrough: 2 no-floating-decimal: 0 - no-func-assign: 2 no-implicit-coercion: 0 - no-implied-eval: 0 - no-inline-comments: 0 - no-inner-declarations: - - 2 - - functions - no-invalid-regexp: 2 + no-implied-eval: 2 no-invalid-this: 0 - no-irregular-whitespace: 2 - no-iterator: 0 - no-label-var: 0 + no-iterator: 2 no-labels: 0 - no-lone-blocks: 0 - no-lonely-if: 0 - no-loop-func: 0 - no-mixed-requires: - - 0 - - false - no-mixed-spaces-and-tabs: - - 2 - - false - linebreak-style: - - 0 - - unix + no-lone-blocks: 2 + no-loop-func: 2 + no-magic-number: 0 no-multi-spaces: 0 no-multi-str: 0 - no-multiple-empty-lines: - - 0 - - max: 2 - no-native-reassign: 0 - no-negated-in-lhs: 2 - no-nested-ternary: 0 - no-new: 0 - no-new-func: 0 - no-new-object: 0 - no-new-require: 0 - no-new-wrappers: 0 - no-obj-calls: 2 + no-native-reassign: 2 + no-new-func: 2 + no-new-wrappers: 2 + no-new: 2 + no-octal-escape: 2 no-octal: 2 - no-octal-escape: 0 - no-param-reassign: 0 - no-path-concat: 0 - no-plusplus: 0 - no-process-env: 0 - no-process-exit: 0 - no-proto: 0 + no-proto: 2 no-redeclare: 2 - no-regex-spaces: 2 - no-reserved-keys: 0 - no-restricted-modules: 0 - no-return-assign: 0 - no-script-url: 0 - no-self-compare: 0 + no-return-assign: 2 + no-script-url: 2 + no-self-compare: 2 no-sequences: 0 - no-shadow: 0 - no-shadow-restricted-names: 0 - no-spaced-func: 0 - no-sparse-arrays: 2 - no-sync: 0 - no-ternary: 0 - no-trailing-spaces: 0 - no-this-before-super: 0 no-throw-literal: 0 - no-undef: 2 - no-undef-init: 0 + no-unused-expressions: 2 + no-useless-call: 2 + no-useless-concat: 2 + no-void: 2 + no-warning-comments: 0 + no-with: 2 + radix: 2 + vars-on-top: 0 + wrap-iife: 2 + yoda: 0 + + # Strict + strict: 0 + + # Variables + init-declarations: 0 + no-catch-shadow: 2 + no-delete-var: 2 + no-label-var: 2 + no-shadow-restricted-names: 2 + no-shadow: 0 + no-undef-init: 2 + no-undef: 0 no-undefined: 0 - no-unexpected-multiline: 0 - no-underscore-dangle: 0 - no-unneeded-ternary: 0 - no-unreachable: 2 - no-unused-expressions: 0 - no-unused-vars: - - 2 - - vars: all - args: after-used + no-unused-vars: 0 no-use-before-define: 0 - no-useless-call: 0 - no-void: 0 - no-var: 0 - no-warning-comments: - - 0 - - terms: - - todo - - fixme - - xxx - location: start - no-with: 0 - array-bracket-spacing: - - 0 - - never - arrow-parens: 0 - arrow-spacing: 0 - accessor-pairs: 0 - block-scoped-var: 0 - brace-style: - - 0 - - 1tbs - callback-return: 0 + + # Node.js and CommonJS + callback-return: 2 + global-require: 2 + handle-callback-err: 2 + no-mixed-requires: 0 + no-new-require: 0 + no-path-concat: 2 + no-process-exit: 2 + no-restricted-modules: 0 + no-sync: 0 + + # Stylistic Issues + array-bracket-spacing: 0 + block-spacing: 0 + brace-style: 0 camelcase: 0 - comma-dangle: - - 2 - - never comma-spacing: 0 comma-style: 0 - complexity: - - 2 - - 11 - computed-property-spacing: - - 0 - - never - consistent-return: 0 - consistent-this: - - 0 - - that - constructor-super: 0 - curly: - - 0 - - all - default-case: 0 - dot-location: 0 - dot-notation: - - 0 - - allowKeywords: true + computed-property-spacing: 0 + consistent-this: 0 eol-last: 0 - eqeqeq: 0 func-names: 0 - func-style: - - 0 - - declaration - generator-star-spacing: 0 - guard-for-in: 0 - handle-callback-err: 0 + func-style: 0 + id-length: 0 + id-match: 0 indent: 0 - init-declarations: 0 - key-spacing: - - 0 - - beforeColon: false - afterColon: true + jsx-quotes: 0 + key-spacing: 0 + linebreak-style: 0 lines-around-comment: 0 - max-depth: - - 0 - - 4 - max-len: - - 0 - - 80 - - 4 - max-nested-callbacks: - - 0 - - 2 - max-params: - - 0 - - 3 - max-statements: - - 0 - - 10 + max-depth: 0 + max-len: 0 + max-nested-callbacks: 0 + max-params: 0 + max-statements: [2, 30] new-cap: 0 new-parens: 0 newline-after-var: 0 - object-curly-spacing: - - 0 - - never - object-shorthand: 0 + no-array-constructor: 0 + no-bitwise: 0 + no-continue: 0 + no-inline-comments: 0 + no-lonely-if: 0 + no-mixed-spaces-and-tabs: 0 + no-multiple-empty-lines: 0 + no-negated-condition: 0 + no-nested-ternary: 0 + no-new-object: 0 + no-plusplus: 0 + no-restricted-syntax: 0 + no-spaced-func: 0 + no-ternary: 0 + no-trailing-spaces: 0 + no-underscore-dangle: 0 + no-unneeded-ternary: 0 + object-curly-spacing: 0 one-var: 0 - operator-assignment: - - 0 - - always + operator-assignment: 0 operator-linebreak: 0 padded-blocks: 0 - prefer-const: 0 - prefer-spread: 0 - prefer-reflect: 0 quote-props: 0 - quotes: - - 0 - - double - radix: 0 - require-yield: 0 + quotes: 0 + require-jsdoc: 0 + semi-spacing: 0 semi: 0 - semi-spacing: - - 0 - - before: false - after: true sort-vars: 0 - space-after-keywords: - - 0 - - always - space-before-blocks: - - 0 - - always - space-before-function-paren: - - 0 - - always - space-in-parens: - - 0 - - never + space-after-keywords: 0 + space-before-blocks: 0 + space-before-function-paren: 0 + space-before-keywords: 0 + space-in-parens: 0 space-infix-ops: 0 space-return-throw-case: 0 - space-unary-ops: - - 0 - - words: true - nonwords: false + space-unary-ops: 0 spaced-comment: 0 - strict: 0 - use-isnan: 2 - valid-jsdoc: 0 - valid-typeof: 2 - vars-on-top: 0 - wrap-iife: 0 wrap-regex: 0 - yoda: - - 0 - - never -env: - browser: true - node: true - jquery: true - amd: true - commonjs: true + + # ECMAScript 6 + arrow-body-style: 0 + arrow-parens: 0 + arrow-spacing: 0 + constructor-super: 0 + generator-star-spacing: 0 + no-arrow-condition: 0 + no-class-assign: 0 + no-const-assign: 0 + no-dupe-class-members: 0 + no-this-before-super: 0 + no-var: 0 + object-shorthand: 0 + prefer-arrow-callback: 0 + prefer-const: 0 + prefer-reflect: 0 + prefer-spread: 0 + prefer-template: 0 + require-yield: 0 diff --git a/Dockerfile b/Dockerfile index a516479..03e1591 100644 --- a/Dockerfile +++ b/Dockerfile @@ -6,8 +6,6 @@ WORKDIR /usr/src/app/ COPY engine.json / COPY package.json /usr/src/app/ -ENV NODE_ENV production - RUN npm install RUN useradd -u 9000 -r -s /bin/false app diff --git a/Makefile b/Makefile index 3dee05f..c23aef7 100644 --- a/Makefile +++ b/Makefile @@ -6,4 +6,4 @@ image: docker build --rm -t $(IMAGE_NAME) . test: image - docker run --rm $(IMAGE_NAME) sh -c "cd /usr/src/app && npm test" + docker run --rm -v $$PWD/test/fixtures:/code $(IMAGE_NAME) sh -c "cd /usr/src/app && npm test" diff --git a/bin/fixme b/bin/fixme index 6f6d016..80c9c04 100755 --- a/bin/fixme +++ b/bin/fixme @@ -1,6 +1,10 @@ #!/usr/bin/env node +var fs = require('fs'); var FixMe = require('../lib/fix-me'); -var fixMe = new FixMe(); +var config; -fixMe.runEngine(); +fs.readFile('/config.json', function(err, data) { + if (!err) config = JSON.parse(data); + new FixMe().run(config) +}); diff --git a/circle.yml b/circle.yml index 64e9fce..dd17ba2 100644 --- a/circle.yml +++ b/circle.yml @@ -6,7 +6,8 @@ machine: PRIVATE_REGISTRY: us.gcr.io/code_climate dependencies: - override: #no-op + override: + - echo "skip dependency installation" test: override: diff --git a/lib/fix-me.js b/lib/fix-me.js index 1cf58a6..e6c995c 100644 --- a/lib/fix-me.js +++ b/lib/fix-me.js @@ -1,61 +1,67 @@ -var fs = require('fs'); var readline = require('readline'); var spawn = require('child_process').spawn; -function FixMe() {} +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', +]; -FixMe.prototype.runEngine = function() { - var config = { - include_paths: ['./'], - strings: ['FIXME', 'TODO', 'HACK', 'XXX', 'BUG'] - }; +function FixMe(writable) { + this.output = writable || process.stdout; +} - if (fs.existsSync('/config.json')) { - var userConfig = JSON.parse(fs.readFileSync('/config.json')); +FixMe.prototype.run = function(engineConfig) { + var paths, strings; - config.include_paths = userConfig.include_paths; + if (engineConfig) { + paths = engineConfig.include_paths; + } else { + paths = DEFAULT_PATHS; + } - if (userConfig.config) { - for (var prop in userConfig.config) { - config[prop] = userConfig.config[prop]; - } - } + if (engineConfig && engineConfig.config && engineConfig.config.strings) { + strings = engineConfig.config.strings; + } else { + strings = DEFAULT_STRINGS; } - this.find(config.include_paths, config.strings); + this.find(paths, strings) } -FixMe.prototype.find = function(files, strings) { - var fixmeStrings = '(' + strings.join('|') + ')'; - var args = ['--line-number', '--with-filename', '--word-regexp', - '--only-matching', '--extended-regexp', '--recursive', - '--binary-files=without-match', fixmeStrings]; - var grep = spawn('grep', args.concat(files)); - - readline.createInterface({ input: grep.stdout }).on('line', function(line) { - var cols = line.split(':'); - var fileName = cols[0].replace(/^\/code\//, ''); - var lineNum = parseInt(cols[1]); - var matchedString = cols[2]; - - if (matchedString !== undefined){ - var issue = JSON.stringify({ - 'type': 'issue', - 'check_name': matchedString, - 'description': matchedString + ' found', - 'categories': ['Bug Risk'], - 'location':{ - 'path': fileName, - 'lines': { - 'begin': lineNum, - 'end': lineNum - } - } - }); - - console.log(issue + '\0'); - } +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]; + 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/package.json b/package.json index 5cd4cb1..62dbccd 100644 --- a/package.json +++ b/package.json @@ -3,13 +3,9 @@ "description": "Static analysis tool that finds FIXME, TODO, BUG, etc. comments in your code.", "version": "0.0.1", "main": "./lib/fix-me.js", - "dependencies": { - "glob": "6.0.1" - }, "devDependencies": { "chai": "3.4.1", - "mocha": "2.3.3", - "intercept-stdout": "0.1.2" + "mocha": "2.3.3" }, "scripts": { "test": "mocha" diff --git a/test/fix-me.js b/test/fix-me.js index f8e1f90..44b4d90 100644 --- a/test/fix-me.js +++ b/test/fix-me.js @@ -1,31 +1,161 @@ -var expect = require('chai').expect, - intercept = require("intercept-stdout"), - fixMe = require('../lib/fix-me.js'), - engine = new fixMe; +var expect = require('chai').expect; +var stream = require('stream'); +var util = require('util'); +var FixMe = require('../lib/fix-me.js'); describe("fixMe", function(){ - describe("#runEngine()", function(){ - xit("checks for /config.json", function(){ - // expect(); + 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); }); }); - describe('#find(file)', function(){ - it('finds and correctly prints TODO issues', function(done){ - var capturedText = "", - unhookIntercept = intercept(function(txt) { - capturedText += txt; - }); + 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/code/src/code/test.js', ["FIXME", "TODO", "HACK", "XXX", "BUG"]); + engine.find(['test/fixtures/file.js'], ['TODO', 'SUP'], function() { + var issues = buf.toIssues(); - // to capture standard output from engine, wait. - setTimeout(function() { - unhookIntercept(); + expect(issues.length).to.eq(2) - expect(capturedText).to.eq('{"type":"issue","check_name":"TODO","description":"TODO found","categories":["Bug Risk"],"location":{"path":"test/fixtures/code/src/code/test.js","lines":{"begin":5,"end":5}}}\0\n'); - done(); - }, 10); + 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(); + }); }); }); }); + +function IssueBuffer() { + this._data = ""; + stream.Writable.call(this); +} + +util.inherits(IssueBuffer, stream.Writable); + +IssueBuffer.prototype._write = function(chunk, encoding, done) { + this._data += chunk.toString(); + done(); +}; + +IssueBuffer.prototype.toIssues = function() { + if (this._data.length === 0) return []; + return this._data.slice(0, -1).split('\0').map((json) => JSON.parse(json)); +} diff --git a/test/fixtures/binary.out b/test/fixtures/binary.out new file mode 100644 index 0000000000000000000000000000000000000000..d62f9045bc6f42410285fe66f11f807bf4b3a757 GIT binary patch literal 8488 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 Date: Mon, 14 Mar 2016 11:13:13 -0400 Subject: [PATCH 35/44] Use eslintrc.js from styleguide * Fix resulting Code Climate issues * Extract IssueBuffer into support file --- .codeclimate.yml | 4 +- .eslintrc | 213 ----------------------------------- .eslintrc.js | 25 ++++ bin/fixme | 5 +- lib/fix-me.js | 10 +- test/fix-me.js | 34 ++---- test/support/issue_buffer.js | 24 ++++ 7 files changed, 70 insertions(+), 245 deletions(-) delete mode 100644 .eslintrc create mode 100644 .eslintrc.js create mode 100644 test/support/issue_buffer.js diff --git a/.codeclimate.yml b/.codeclimate.yml index dc20aff..69f051b 100644 --- a/.codeclimate.yml +++ b/.codeclimate.yml @@ -7,5 +7,5 @@ ratings: paths: - "**.js" exclude_paths: -- "test" -- "node_modules" +- "node_modules/" +- "test/fixtures/" diff --git a/.eslintrc b/.eslintrc deleted file mode 100644 index 9221332..0000000 --- a/.eslintrc +++ /dev/null @@ -1,213 +0,0 @@ -ecmaFeatures: - modules: true - jsx: true - -env: - amd: true - browser: true - es6: true - jquery: true - node: true - -# http://eslint.org/docs/rules/ -rules: - # Possible Errors - comma-dangle: 0 - no-cond-assign: 2 - no-console: 0 - no-constant-condition: 2 - no-control-regex: 2 - no-debugger: 2 - no-dupe-args: 2 - no-dupe-keys: 2 - no-duplicate-case: 2 - no-empty: 2 - no-empty-character-class: 2 - no-ex-assign: 2 - no-extra-boolean-cast: 2 - no-extra-parens: 0 - no-extra-semi: 2 - no-func-assign: 2 - no-inner-declarations: [2, functions] - no-invalid-regexp: 2 - no-irregular-whitespace: 2 - no-negated-in-lhs: 2 - no-obj-calls: 2 - no-regex-spaces: 2 - no-sparse-arrays: 2 - no-unexpected-multiline: 2 - no-unreachable: 2 - use-isnan: 2 - valid-jsdoc: 0 - valid-typeof: 2 - - # Best Practices - accessor-pairs: 2 - block-scoped-var: 0 - complexity: [2, 6] - consistent-return: 0 - curly: 0 - default-case: 0 - dot-location: 0 - dot-notation: 0 - eqeqeq: 2 - guard-for-in: 2 - no-alert: 2 - no-caller: 2 - no-case-declarations: 2 - no-div-regex: 2 - no-else-return: 0 - no-empty-label: 2 - no-empty-pattern: 2 - no-eq-null: 2 - no-eval: 2 - no-extend-native: 2 - no-extra-bind: 2 - no-fallthrough: 2 - no-floating-decimal: 0 - no-implicit-coercion: 0 - no-implied-eval: 2 - no-invalid-this: 0 - no-iterator: 2 - no-labels: 0 - no-lone-blocks: 2 - no-loop-func: 2 - no-magic-number: 0 - no-multi-spaces: 0 - no-multi-str: 0 - no-native-reassign: 2 - no-new-func: 2 - no-new-wrappers: 2 - no-new: 2 - no-octal-escape: 2 - no-octal: 2 - no-proto: 2 - no-redeclare: 2 - no-return-assign: 2 - no-script-url: 2 - no-self-compare: 2 - no-sequences: 0 - no-throw-literal: 0 - no-unused-expressions: 2 - no-useless-call: 2 - no-useless-concat: 2 - no-void: 2 - no-warning-comments: 0 - no-with: 2 - radix: 2 - vars-on-top: 0 - wrap-iife: 2 - yoda: 0 - - # Strict - strict: 0 - - # Variables - init-declarations: 0 - no-catch-shadow: 2 - no-delete-var: 2 - no-label-var: 2 - no-shadow-restricted-names: 2 - no-shadow: 0 - no-undef-init: 2 - no-undef: 0 - no-undefined: 0 - no-unused-vars: 0 - no-use-before-define: 0 - - # Node.js and CommonJS - callback-return: 2 - global-require: 2 - handle-callback-err: 2 - no-mixed-requires: 0 - no-new-require: 0 - no-path-concat: 2 - no-process-exit: 2 - no-restricted-modules: 0 - no-sync: 0 - - # Stylistic Issues - array-bracket-spacing: 0 - block-spacing: 0 - brace-style: 0 - camelcase: 0 - comma-spacing: 0 - comma-style: 0 - computed-property-spacing: 0 - consistent-this: 0 - eol-last: 0 - func-names: 0 - func-style: 0 - id-length: 0 - id-match: 0 - indent: 0 - jsx-quotes: 0 - key-spacing: 0 - linebreak-style: 0 - lines-around-comment: 0 - max-depth: 0 - max-len: 0 - max-nested-callbacks: 0 - max-params: 0 - max-statements: [2, 30] - new-cap: 0 - new-parens: 0 - newline-after-var: 0 - no-array-constructor: 0 - no-bitwise: 0 - no-continue: 0 - no-inline-comments: 0 - no-lonely-if: 0 - no-mixed-spaces-and-tabs: 0 - no-multiple-empty-lines: 0 - no-negated-condition: 0 - no-nested-ternary: 0 - no-new-object: 0 - no-plusplus: 0 - no-restricted-syntax: 0 - no-spaced-func: 0 - no-ternary: 0 - no-trailing-spaces: 0 - no-underscore-dangle: 0 - no-unneeded-ternary: 0 - object-curly-spacing: 0 - one-var: 0 - operator-assignment: 0 - operator-linebreak: 0 - padded-blocks: 0 - quote-props: 0 - quotes: 0 - require-jsdoc: 0 - semi-spacing: 0 - semi: 0 - sort-vars: 0 - space-after-keywords: 0 - space-before-blocks: 0 - space-before-function-paren: 0 - space-before-keywords: 0 - space-in-parens: 0 - space-infix-ops: 0 - space-return-throw-case: 0 - space-unary-ops: 0 - spaced-comment: 0 - wrap-regex: 0 - - # ECMAScript 6 - arrow-body-style: 0 - arrow-parens: 0 - arrow-spacing: 0 - constructor-super: 0 - generator-star-spacing: 0 - no-arrow-condition: 0 - no-class-assign: 0 - no-const-assign: 0 - no-dupe-class-members: 0 - no-this-before-super: 0 - no-var: 0 - object-shorthand: 0 - prefer-arrow-callback: 0 - prefer-const: 0 - prefer-reflect: 0 - prefer-spread: 0 - prefer-template: 0 - require-yield: 0 diff --git a/.eslintrc.js b/.eslintrc.js new file mode 100644 index 0000000..d59da74 --- /dev/null +++ b/.eslintrc.js @@ -0,0 +1,25 @@ +module.exports = { + "env": { + "node": true, // When in a backend context + "es6": true, + }, + "rules": { + "brace-style": [2, "1tbs", { "allowSingleLine": true }], + "comma-style": [2, "first", { exceptions: {ArrayExpression: true, ObjectExpression: true} }], + "complexity": [2, 6], + "curly": 2, + "eqeqeq": [2, "allow-null"], + "no-shadow-restricted-names": 2, + "no-undef": 2, + "no-use-before-define": 2, + "radix": 2, + "semi": 2, + "space-infix-ops": 2, + "strict": 0, + }, + /** + * globals should be defined per file when possible. Use the directive here + * when there are project-level globals (such as jquery) + */ + "globals": {}, +}; diff --git a/bin/fixme b/bin/fixme index 80c9c04..f60c1c9 100755 --- a/bin/fixme +++ b/bin/fixme @@ -5,6 +5,9 @@ var FixMe = require('../lib/fix-me'); var config; fs.readFile('/config.json', function(err, data) { - if (!err) config = JSON.parse(data); + if (!err) { + config = JSON.parse(data); + } + new FixMe().run(config) }); diff --git a/lib/fix-me.js b/lib/fix-me.js index e6c995c..cd94956 100644 --- a/lib/fix-me.js +++ b/lib/fix-me.js @@ -32,8 +32,8 @@ FixMe.prototype.run = function(engineConfig) { strings = DEFAULT_STRINGS; } - this.find(paths, strings) -} + this.find(paths, strings); +}; FixMe.prototype.find = function(paths, strings, callback) { var pattern = `(${strings.join('|')})`; @@ -61,7 +61,9 @@ FixMe.prototype.find = function(paths, strings, callback) { this.output.write(JSON.stringify(issue) + '\0'); }); - if (callback) grep.stdout.on('close', _ => callback()); -} + if (callback) { + grep.stdout.on('close', _ => callback()); + } +}; module.exports = FixMe; diff --git a/test/fix-me.js b/test/fix-me.js index 44b4d90..8cad9c9 100644 --- a/test/fix-me.js +++ b/test/fix-me.js @@ -1,7 +1,8 @@ +/* global define, it, describe, context */ + var expect = require('chai').expect; -var stream = require('stream'); -var util = require('util'); var FixMe = require('../lib/fix-me.js'); +var IssueBuffer = require('./support/issue_buffer'); describe("fixMe", function(){ describe("#run(engineConfig)", function() { @@ -12,7 +13,7 @@ describe("fixMe", function(){ engine.find = function(_, strings) { expect(strings).to.have.members(['BUG', 'FIXME', 'HACK', 'TODO', 'XXX']); done(); - } + }; engine.run(); }); @@ -23,7 +24,7 @@ describe("fixMe", function(){ engine.find = function(paths) { expect(paths).to.have.members(['./']); done(); - } + }; engine.run(); }); @@ -36,9 +37,9 @@ describe("fixMe", function(){ }; engine.find = function(paths) { - expect(paths).to.have.members(['test/fixtures/code/src/code/test.js']) + expect(paths).to.have.members(['test/fixtures/code/src/code/test.js']); done(); - } + }; engine.run(config); }); @@ -54,7 +55,7 @@ describe("fixMe", function(){ engine.find = function(_, strings) { expect(strings).to.have.members(['SUP']); done(); - } + }; engine.run(engineConfig); }); @@ -68,7 +69,7 @@ describe("fixMe", function(){ engine.find(['test/fixtures/file.js'], ['TODO', 'SUP'], function() { var issues = buf.toIssues(); - expect(issues.length).to.eq(2) + expect(issues.length).to.eq(2); expect(issues[0].categories).to.have.members(['Bug Risk']); expect(issues[0].check_name).to.eq('TODO'); @@ -142,20 +143,3 @@ describe("fixMe", function(){ }); }); }); - -function IssueBuffer() { - this._data = ""; - stream.Writable.call(this); -} - -util.inherits(IssueBuffer, stream.Writable); - -IssueBuffer.prototype._write = function(chunk, encoding, done) { - this._data += chunk.toString(); - done(); -}; - -IssueBuffer.prototype.toIssues = function() { - if (this._data.length === 0) return []; - return this._data.slice(0, -1).split('\0').map((json) => JSON.parse(json)); -} diff --git a/test/support/issue_buffer.js b/test/support/issue_buffer.js new file mode 100644 index 0000000..b1a976e --- /dev/null +++ b/test/support/issue_buffer.js @@ -0,0 +1,24 @@ +var stream = require('stream'); +var util = require('util'); + +function IssueBuffer() { + this._data = ""; + stream.Writable.call(this); +} + +util.inherits(IssueBuffer, stream.Writable); + +IssueBuffer.prototype._write = function(chunk, encoding, done) { + this._data += chunk.toString(); + done(); +}; + +IssueBuffer.prototype.toIssues = function() { + if (this._data.length === 0) { + return []; + } + + return this._data.slice(0, -1).split('\0').map((json) => JSON.parse(json)); +}; + +module.exports = IssueBuffer; From 65a6956310dfde325547a89d537849767c4ba84a Mon Sep 17 00:00:00 2001 From: Conner Wingard Date: Mon, 4 Apr 2016 15:05:48 -0400 Subject: [PATCH 36/44] Correct GitHub Issues link in README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 5621154..f7907bd 100644 --- a/README.md +++ b/README.md @@ -40,4 +40,4 @@ engines: 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-watson/issues), as your question may have already been covered. If not, [go ahead and open a support ticket with us](https://codeclimate.com/help). +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). From 38d83ab193243971ef14fcea2e161f41e60786b8 Mon Sep 17 00:00:00 2001 From: patrick brisbin Date: Wed, 22 Jun 2016 09:14:21 -0400 Subject: [PATCH 37/44] Ignore unparsable stdout Looking at the full stack from this bugsnag[1] NoMethodError: undefined method `-' for nil:NilClass Originating from: begin_index = lines.fetch("begin") - 1 This tells me an engine output { begin: null }. The events for this errored build show it was a fixme engine that did this. It looks like grep outputting to stdout something that doesn't match the expected format could easily cause this: var parts = line.split(':'); // => ["the whole line"] var lineNumber = parseInt(parts[1], 10); // => NaN var issue = { 'location': { 'begin': lineNumber, // => 'begin': NaN } this.output.write(JSON.stringify(issue) //... // {"begin":null} An assertion that we have all the values we expected before printing the issue should prevent downstream errors. NOTE: CLI validations would catch this, since they run before the output filter. In builder's case, the output filter runs before validations so invalid issues can get in there and cause these errors. Addressing this is a larger track of work, so fixing misbehaving engines is valuable in the meantime. 1: https://bugsnag.com/code-climate/builder/errors/57546cf2be3f29e6a63111c8?event_id=5769f0b4076813321236b4e1 --- lib/fix-me.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/lib/fix-me.js b/lib/fix-me.js index cd94956..9c5af5f 100644 --- a/lib/fix-me.js +++ b/lib/fix-me.js @@ -44,6 +44,12 @@ FixMe.prototype.find = function(paths, strings, callback) { 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; + } + var issue = { 'categories': ['Bug Risk'], 'check_name': matchedString, From dd0e42c9e4426d4e61a33ee51ff6ed108a0918de Mon Sep 17 00:00:00 2001 From: Max Jacobson Date: Wed, 21 Dec 2016 13:21:02 -0500 Subject: [PATCH 38/44] Ignore fixme comments in .codeclimate.yml If you're using custom strings, we'll find those strings in config file where you specify those custom strings, and create some issues. That seems less than helpful. --- lib/fix-me.js | 2 ++ test/fix-me.js | 12 ++++++++++++ test/fixtures/.codeclimate.yml | 6 ++++++ test/fixtures/urgent.js | 2 ++ 4 files changed, 22 insertions(+) create mode 100644 test/fixtures/.codeclimate.yml create mode 100644 test/fixtures/urgent.js diff --git a/lib/fix-me.js b/lib/fix-me.js index 9c5af5f..5bd6e9f 100644 --- a/lib/fix-me.js +++ b/lib/fix-me.js @@ -50,6 +50,8 @@ FixMe.prototype.find = function(paths, strings, callback) { return; } + if(path.indexOf('.codeclimate.yml') !== -1) { return; } + var issue = { 'categories': ['Bug Risk'], 'check_name': matchedString, diff --git a/test/fix-me.js b/test/fix-me.js index 8cad9c9..2e13887 100644 --- a/test/fix-me.js +++ b/test/fix-me.js @@ -59,6 +59,18 @@ describe("fixMe", function(){ engine.run(engineConfig); }); + + it('ignores .codeclimate.yml', function(done) { + var buf = new IssueBuffer(); + var engine = new FixMe(buf); + + engine.find(['test/fixtures/'], ['URGENT'], function() { + var issues = buf.toIssues(); + expect(issues.length).to.eq(1); + expect(issues[0].location.path).to.eq('test/fixtures/urgent.js'); + done(); + }); + }); }); describe('#find(paths, strings)', function() { diff --git a/test/fixtures/.codeclimate.yml b/test/fixtures/.codeclimate.yml new file mode 100644 index 0000000..df85cb4 --- /dev/null +++ b/test/fixtures/.codeclimate.yml @@ -0,0 +1,6 @@ +engines: + fixme: + enabled: true + config: + strings: + - URGENT diff --git a/test/fixtures/urgent.js b/test/fixtures/urgent.js new file mode 100644 index 0000000..ab28040 --- /dev/null +++ b/test/fixtures/urgent.js @@ -0,0 +1,2 @@ +// URGENT: this is busted +console.log("busted"); From cb97e42aace292668bdd3d266ae3b1ea4e55a601 Mon Sep 17 00:00:00 2001 From: Max Jacobson Date: Wed, 21 Dec 2016 15:10:08 -0500 Subject: [PATCH 39/44] Don't ignore comments in .codeclimate.yml --- lib/fix-me.js | 12 +++++++++++- test/fix-me.js | 9 ++++++--- test/fixtures/.codeclimate.yml | 1 + 3 files changed, 18 insertions(+), 4 deletions(-) diff --git a/lib/fix-me.js b/lib/fix-me.js index 5bd6e9f..9e2727c 100644 --- a/lib/fix-me.js +++ b/lib/fix-me.js @@ -1,5 +1,6 @@ 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']; @@ -35,6 +36,15 @@ FixMe.prototype.run = function(engineConfig) { this.find(paths, strings); }; +var isItsOwnConfig = function(path, lineNumber) { + if (path.indexOf(".codeclimate.yml") === -1) { return false; } + var lines = fs.readFileSync(path, "utf8").split("\n"); + var line = lines[lineNumber - 1]; + if(!line) { return false; } + if (line.match(/^\s*#/)) { return false; } + return true; +}; + FixMe.prototype.find = function(paths, strings, callback) { var pattern = `(${strings.join('|')})`; var grep = spawn('grep', [...GREP_OPTIONS, pattern, ...paths]); @@ -50,7 +60,7 @@ FixMe.prototype.find = function(paths, strings, callback) { return; } - if(path.indexOf('.codeclimate.yml') !== -1) { return; } + if(isItsOwnConfig(path, lineNumber)) { return; } var issue = { 'categories': ['Bug Risk'], diff --git a/test/fix-me.js b/test/fix-me.js index 2e13887..1df3177 100644 --- a/test/fix-me.js +++ b/test/fix-me.js @@ -60,14 +60,17 @@ describe("fixMe", function(){ engine.run(engineConfig); }); - it('ignores .codeclimate.yml', function(done) { + 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(); - expect(issues.length).to.eq(1); - expect(issues[0].location.path).to.eq('test/fixtures/urgent.js'); + expect(issues.length).to.eq(2); + + expect(issues[0].location.path).to.eq('test/fixtures/.codeclimate.yml'); + expect(issues[0].location.lines.begin).to.eq(2); + expect(issues[1].location.path).to.eq('test/fixtures/urgent.js'); done(); }); }); diff --git a/test/fixtures/.codeclimate.yml b/test/fixtures/.codeclimate.yml index df85cb4..c085efa 100644 --- a/test/fixtures/.codeclimate.yml +++ b/test/fixtures/.codeclimate.yml @@ -1,4 +1,5 @@ engines: + # URGENT: enable duplication engine fixme: enabled: true config: From 348050210dbb56ff3480dbfdc7f3d34a0366f857 Mon Sep 17 00:00:00 2001 From: Max Jacobson Date: Wed, 21 Dec 2016 15:39:33 -0500 Subject: [PATCH 40/44] abh suggestions --- lib/fix-me.js | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/lib/fix-me.js b/lib/fix-me.js index 9e2727c..2a06012 100644 --- a/lib/fix-me.js +++ b/lib/fix-me.js @@ -36,13 +36,14 @@ FixMe.prototype.run = function(engineConfig) { this.find(paths, strings); }; -var isItsOwnConfig = function(path, lineNumber) { - if (path.indexOf(".codeclimate.yml") === -1) { return false; } +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]; - if(!line) { return false; } - if (line.match(/^\s*#/)) { return false; } - return true; + var line = lines[lineNumber - 1] || ""; + return line.match(/^\s*#/); }; FixMe.prototype.find = function(paths, strings, callback) { @@ -60,7 +61,7 @@ FixMe.prototype.find = function(paths, strings, callback) { return; } - if(isItsOwnConfig(path, lineNumber)) { return; } + if(isItsOwnConfigFile(path) && !isAYamlComment(path, lineNumber)) { return; } var issue = { 'categories': ['Bug Risk'], From a15bf4f5469a49490a589545f66fc36b21a83a0e Mon Sep 17 00:00:00 2001 From: Roberto Quintanilla Date: Wed, 20 Sep 2017 17:46:26 -0500 Subject: [PATCH 41/44] Reduced docker image size (#51) * Change Dockerfile base to node:6-alpine * Replace BusyBox grep (Alpine) with GNU grep * Update maintainer LABEL * Use COPY destinations relative to WORKDIR --- Dockerfile | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/Dockerfile b/Dockerfile index 03e1591..91ab0c6 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,17 +1,18 @@ -FROM node:5.5-slim -MAINTAINER Michael R. Bernstein +FROM node:6-alpine +LABEL maintainer="Code Climate " WORKDIR /usr/src/app/ COPY engine.json / -COPY package.json /usr/src/app/ +COPY package.json ./ -RUN npm install +# Install dependencies: +RUN apk add --no-cache --virtual .run-deps grep && npm install -RUN useradd -u 9000 -r -s /bin/false app +RUN adduser -u 9000 -S -s /bin/false app USER app -COPY . /usr/src/app +COPY . ./ VOLUME /code WORKDIR /code From 31b86d25b3b6b0eef868fdcbf42e9f899ed78c6e Mon Sep 17 00:00:00 2001 From: Dante Ventieri Date: Fri, 17 Feb 2023 10:59:43 -0300 Subject: [PATCH 42/44] QUA-958: Push images to Dockerhub instead of GCR (#55) * Push images to Dockerhub instead of GCR * delete old circleci config * override RELEASE_TAG on empty string * Force deploy * find out why tests are failing * fix tests --- .circleci/config.yml | 53 ++++++++++++++++++++++++++++++++++++++++++++ Makefile | 11 ++++++++- circle.yml | 29 ------------------------ test/fix-me.js | 12 +++++----- 4 files changed, 70 insertions(+), 35 deletions(-) create mode 100644 .circleci/config.yml delete mode 100644 circle.yml diff --git a/.circleci/config.yml b/.circleci/config.yml new file mode 100644 index 0000000..4c8045e --- /dev/null +++ b/.circleci/config.yml @@ -0,0 +1,53 @@ +version: 2.1 + +jobs: + build: + machine: + docker_layer_caching: true + working_directory: ~/codeclimate/codeclimate-fixme + steps: + - checkout + - run: + name: Build + command: make image + - run: + name: Test + command: make test + + release_images: + machine: + docker_layer_caching: true + working_directory: ~/codeclimate/codeclimate-fixme + steps: + - checkout + - run: + name: Validate owner + command: | + if [ "$CIRCLE_PROJECT_USERNAME" -ne "codeclimate" ] + then + echo "Skipping release for non-codeclimate branches" + circleci step halt + fi + - run: make image + - run: echo "$DOCKER_PASSWORD" | docker login -u "$DOCKER_USERNAME" --password-stdin + - run: + name: Push image to Dockerhub + command: | + make release RELEASE_TAG="b$CIRCLE_BUILD_NUM" + make release RELEASE_TAG="$(echo $CIRCLE_BRANCH | grep -oP 'channel/\K[\w\-]+')" +workflows: + version: 2 + build_deploy: + jobs: + - build + - release_images: + context: Quality + requires: + - build + filters: + branches: + only: /master|channel\/[\w-]+/ + +notify: + webhooks: + - url: https://cc-slack-proxy.herokuapp.com/circle diff --git a/Makefile b/Makefile index c23aef7..0be2e65 100644 --- a/Makefile +++ b/Makefile @@ -1,9 +1,18 @@ -.PHONY: image test +.PHONY: image test release IMAGE_NAME ?= codeclimate/codeclimate-fixme +RELEASE_REGISTRY ?= codeclimate + +ifndef RELEASE_TAG +override RELEASE_TAG = latest +endif image: docker build --rm -t $(IMAGE_NAME) . test: image docker run --rm -v $$PWD/test/fixtures:/code $(IMAGE_NAME) sh -c "cd /usr/src/app && npm test" + +release: + docker tag $(IMAGE_NAME) $(RELEASE_REGISTRY)/codeclimate-fixme:$(RELEASE_TAG) + docker push $(RELEASE_REGISTRY)/codeclimate-fixme:$(RELEASE_TAG) diff --git a/circle.yml b/circle.yml deleted file mode 100644 index dd17ba2..0000000 --- a/circle.yml +++ /dev/null @@ -1,29 +0,0 @@ -machine: - services: - - docker - environment: - CLOUDSDK_CORE_DISABLE_PROMPTS: 1 - PRIVATE_REGISTRY: us.gcr.io/code_climate - -dependencies: - override: - - echo "skip dependency installation" - -test: - override: - - IMAGE_NAME="$PRIVATE_REGISTRY/$CIRCLE_PROJECT_REPONAME:b$CIRCLE_BUILD_NUM" make test - -deployment: - registry: - branch: master - owner: codeclimate - commands: - - echo $GCLOUD_JSON_KEY_BASE64 | sed 's/ //g' | base64 -d > /tmp/gcloud_key.json - - curl https://sdk.cloud.google.com | bash - - gcloud auth activate-service-account --key-file /tmp/gcloud_key.json - - gcloud docker -a - - docker push $PRIVATE_REGISTRY/$CIRCLE_PROJECT_REPONAME:b$CIRCLE_BUILD_NUM - -notify: - webhooks: - - url: https://cc-slack-proxy.herokuapp.com/circle diff --git a/test/fix-me.js b/test/fix-me.js index 1df3177..3340c85 100644 --- a/test/fix-me.js +++ b/test/fix-me.js @@ -65,12 +65,14 @@ describe("fixMe", function(){ var engine = new FixMe(buf); engine.find(['test/fixtures/'], ['URGENT'], function() { - var issues = buf.toIssues(); + 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(issues[0].location.path).to.eq('test/fixtures/.codeclimate.yml'); - expect(issues[0].location.lines.begin).to.eq(2); - expect(issues[1].location.path).to.eq('test/fixtures/urgent.js'); + 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(); }); }); From 60275ecefa990724c916b475af7e8c8f518df105 Mon Sep 17 00:00:00 2001 From: Laura Date: Wed, 9 Apr 2025 00:45:48 -0300 Subject: [PATCH 43/44] Update with deprecation notice.md (#56) --- README.md | 54 +++++++++++++++++++++--------------------------------- 1 file changed, 21 insertions(+), 33 deletions(-) diff --git a/README.md b/README.md index f7907bd..163fdd3 100644 --- a/README.md +++ b/README.md @@ -1,43 +1,31 @@ -# Code Climate FIXME Engine +# Try Qlty today, the newest edition of Code Climate Quality. +#### This repository is deprecated and archived. -`codeclimate-fixme` is a Code Climate engine that performs a case-sensitive search for the following strings in your project: +This is a repository for a Code Climate Quality plugin which is packaged as a Docker image. -* `TODO` -* `FIXME` -* `HACK` -* `BUG` -* `XXX` +Code Climate Quality is being replaced with the new [Qlty](qlty.sh) code quality platform. Qlty uses a new plugin system which does not require packaging plugins as Docker images. -These strings are things you should fix now, not later. +As a result, this repository is no longer maintained and has been archived. -`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. +## Advantages of Qlty plugins +The new Qlty plugins system provides key advantages over the older, Docker-based plugin system: -### Installation & Usage +- Linting runs much faster without the overhead of virtualization +- New versions of linters are available immediately without needing to wait for a re-packaged release +- Plugins can be run with any arbitrary extensions (like extra rules and configs) without requiring pre-packaging +- Eliminates security issues associated with exposing a Docker daemon -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`. +## Try out Qlty today free -### Configuration +[Qlty CLI](https://docs.qlty.sh/cli/quickstart) is the fastest linter and auto-formatter for polyglot teams. It is completely free and available for Mac, Windows, and Linux. -You can specify what strings to match by adding a `strings` key in your -`.codeclimate.yml`: + - Install Qlty CLI: +` +curl https://qlty.sh | sh # Mac or Linux +` +or ` ` -```yaml -engines: - fixme: - enabled: true - config: - strings: - - FIXME - - CUSTOM -``` +[Qlty Cloud](https://docs.qlty.sh/cloud/quickstart) is a full code health platform for integrating code quality into development team workflows. It is free for unlimited private contributors. + - [Try Qlty Cloud today](https://docs.qlty.sh/cloud/quickstart) -**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). +**Note**: For existing customers of Quality, please see our [Migration Guide](https://docs.qlty.sh/migration/guide) for more information and resources. From 03c5c83f6bb1bd36c09d7574c2aec16449b96583 Mon Sep 17 00:00:00 2001 From: Laura Date: Wed, 23 Apr 2025 13:52:47 -0300 Subject: [PATCH 44/44] Patch 1 (#57) * Update with deprecation notice.md * Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 163fdd3..a30c216 100644 --- a/README.md +++ b/README.md @@ -23,7 +23,7 @@ The new Qlty plugins system provides key advantages over the older, Docker-based ` curl https://qlty.sh | sh # Mac or Linux ` -or ` ` +or ` powershell -c "iwr https://qlty.sh | iex" # Windows` [Qlty Cloud](https://docs.qlty.sh/cloud/quickstart) is a full code health platform for integrating code quality into development team workflows. It is free for unlimited private contributors. - [Try Qlty Cloud today](https://docs.qlty.sh/cloud/quickstart)