From 01a75b587bb8bf33dbafad1a51c5a8765f1461b8 Mon Sep 17 00:00:00 2001 From: Bryan Helmkamp Date: Sun, 23 Aug 2015 05:54:15 +0000 Subject: [PATCH 01/46] Fixup circle config --- circle.yml | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/circle.yml b/circle.yml index 6d06d9d..5d5f815 100644 --- a/circle.yml +++ b/circle.yml @@ -3,22 +3,21 @@ machine: - docker environment: CLOUDSDK_CORE_DISABLE_PROMPTS: 1 - image_name: codeclimate-csslint dependencies: - pre: + override: - 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 test: override: - - docker build -t=$registry_root/$image_name:b$CIRCLE_BUILD_NUM . + - docker build -t=$registry_root/$CIRCLE_PROJECT_REPONAME:b$CIRCLE_BUILD_NUM . deployment: registry: branch: master commands: + - gcloud auth activate-service-account --key-file /tmp/gcloud_key.json + - gcloud docker -a - docker push $registry_root/$image_name:b$CIRCLE_BUILD_NUM From 71f7066aedff66b4619bee0eb1b97b6774a87618 Mon Sep 17 00:00:00 2001 From: Bryan Helmkamp Date: Sun, 23 Aug 2015 06:02:49 +0000 Subject: [PATCH 02/46] Fix config bug --- circle.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/circle.yml b/circle.yml index 5d5f815..8e73516 100644 --- a/circle.yml +++ b/circle.yml @@ -19,5 +19,5 @@ deployment: commands: - gcloud auth activate-service-account --key-file /tmp/gcloud_key.json - gcloud docker -a - - docker push $registry_root/$image_name:b$CIRCLE_BUILD_NUM + - docker push $registry_root/$CIRCLE_PROJECT_REPONAME:b$CIRCLE_BUILD_NUM From 3287ef7c98def1924a0063b364bbb789feb9f508 Mon Sep 17 00:00:00 2001 From: Adam Garstka Date: Tue, 25 Aug 2015 14:12:12 +0200 Subject: [PATCH 03/46] Sync Gemfile.lock --- Gemfile.lock | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index a96e4b5..cd340ba 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,15 +1,13 @@ GEM remote: https://rubygems.org/ specs: - ansi (1.5.0) - ast (2.0.0) coderay (1.1.0) diff-lcs (1.2.5) json (1.8.3) method_source (0.8.2) - oga (1.0.2) - ast - ruby-ll (~> 2.1) + mini_portile (0.6.2) + nokogiri (1.6.6.2) + mini_portile (~> 0.6.0) pry (0.10.1) coderay (~> 1.1.0) method_source (~> 0.8.1) @@ -28,9 +26,6 @@ GEM diff-lcs (>= 1.2.0, < 2.0) rspec-support (~> 3.2.0) rspec-support (3.2.2) - ruby-ll (2.1.2) - ansi - ast slop (3.6.0) PLATFORMS @@ -38,10 +33,10 @@ PLATFORMS DEPENDENCIES json - oga + nokogiri pry rake rspec BUNDLED WITH - 1.10.2 + 1.10.6 From 89d3d8db3ad78dd3358e1835b67845207213284c Mon Sep 17 00:00:00 2001 From: Adam Garstka Date: Tue, 25 Aug 2015 14:36:54 +0200 Subject: [PATCH 04/46] Add spec for including only *.css files --- spec/cc/engine/csslint_spec.rb | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 spec/cc/engine/csslint_spec.rb diff --git a/spec/cc/engine/csslint_spec.rb b/spec/cc/engine/csslint_spec.rb new file mode 100644 index 0000000..730b8e9 --- /dev/null +++ b/spec/cc/engine/csslint_spec.rb @@ -0,0 +1,28 @@ +require 'cc/engine/csslint' +require 'tmpdir' + +module CC + module Engine + describe CSSlint do + let(:code) { Dir.mktmpdir } + let(:lint) { CSSlint.new(directory: code, io: nil, engine_config: {}) } + let(:content) { '#id { color: red; }' } + + describe '#run' do + it 'analyzes *.css files' do + create_source_file('foo.css', content) + expect{ lint.run }.to output(/Don't use IDs in selectors./).to_stdout + end + + it "doesn't analyze *.scss files" do + create_source_file('foo.scss', content) + expect{ lint.run }.to_not output.to_stdout + end + + def create_source_file(path, content) + File.write(File.join(code, path), content) + end + end + end + end +end From 9b6a235c3adc2e358b3dd9f1223820fa673db64b Mon Sep 17 00:00:00 2001 From: Adam Garstka Date: Tue, 25 Aug 2015 14:56:10 +0200 Subject: [PATCH 05/46] Run specs on circle --- circle.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/circle.yml b/circle.yml index 8e73516..fe3ee84 100644 --- a/circle.yml +++ b/circle.yml @@ -11,6 +11,7 @@ dependencies: test: override: + - bundle exec rspec spec - docker build -t=$registry_root/$CIRCLE_PROJECT_REPONAME:b$CIRCLE_BUILD_NUM . deployment: From 23d4d64d392a02e49b05b50eeb5fdbd38bba3af2 Mon Sep 17 00:00:00 2001 From: Adam Garstka Date: Tue, 25 Aug 2015 16:03:10 +0200 Subject: [PATCH 06/46] Run bundle install on circle --- circle.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/circle.yml b/circle.yml index fe3ee84..2059c56 100644 --- a/circle.yml +++ b/circle.yml @@ -8,6 +8,7 @@ dependencies: override: - echo $gcloud_json_key_base64 | sed 's/ //g' | base64 -d > /tmp/gcloud_key.json - curl https://sdk.cloud.google.com | bash + - bundle install test: override: From c1d8c8d8251eb7310553c65a4f63c796675be256 Mon Sep 17 00:00:00 2001 From: Adam Garstka Date: Tue, 25 Aug 2015 16:16:46 +0200 Subject: [PATCH 07/46] Specify ruby version --- .ruby-version | 1 + 1 file changed, 1 insertion(+) create mode 100644 .ruby-version diff --git a/.ruby-version b/.ruby-version new file mode 100644 index 0000000..5859406 --- /dev/null +++ b/.ruby-version @@ -0,0 +1 @@ +2.2.3 From 6056478cb1b3538b82b39fb82c07500cf38ddfcd Mon Sep 17 00:00:00 2001 From: Adam Garstka Date: Tue, 25 Aug 2015 16:22:12 +0200 Subject: [PATCH 08/46] Add csslint to circle dependencies --- circle.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/circle.yml b/circle.yml index 2059c56..1e75781 100644 --- a/circle.yml +++ b/circle.yml @@ -9,6 +9,7 @@ dependencies: - echo $gcloud_json_key_base64 | sed 's/ //g' | base64 -d > /tmp/gcloud_key.json - curl https://sdk.cloud.google.com | bash - bundle install + - npm install -g codeclimate/csslint.git#7a3a6be test: override: From 28a5ea0eaa2b2f2f11656e14bbe19ef8b5162cb0 Mon Sep 17 00:00:00 2001 From: Aidan Feldman Date: Tue, 18 Aug 2015 16:08:37 -0400 Subject: [PATCH 09/46] only scan files with a .css extension I was noticing .scss being scanned, which isn't relevant because it's much broader syntax. /cc https://github.com/ivantsepp/codeclimate-scss-lint --- lib/cc/engine/csslint.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/cc/engine/csslint.rb b/lib/cc/engine/csslint.rb index c7a9980..0eecfc4 100644 --- a/lib/cc/engine/csslint.rb +++ b/lib/cc/engine/csslint.rb @@ -55,7 +55,7 @@ def csslint_xml end def files - Dir.glob("**/*css") + Dir.glob("**/*.css") end end end From c74d3034203b202027ea1b37e48fbe9ba3f4c2f2 Mon Sep 17 00:00:00 2001 From: Will Fleming Date: Mon, 31 Aug 2015 13:33:38 -0400 Subject: [PATCH 10/46] Fix docker build failing due to missing io-console There's been a change in alpine-ruby somewhere along the line that's causing this problem. For now, we're going to lock to a known tag of our own for the base image. --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 3007800..5382140 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM alpine:edge +FROM codeclimate/alpine-ruby:b38 WORKDIR /usr/src/app COPY Gemfile /usr/src/app/ From bcc620fd2e5e5c9478145cb971ad50d4241d9cd0 Mon Sep 17 00:00:00 2001 From: Francis Hwang Date: Wed, 16 Sep 2015 14:17:49 -0400 Subject: [PATCH 11/46] Add spec to capture extant excluded_files behavior. --- spec/cc/engine/csslint_spec.rb | 31 +++++++++++++++++++++++++------ 1 file changed, 25 insertions(+), 6 deletions(-) diff --git a/spec/cc/engine/csslint_spec.rb b/spec/cc/engine/csslint_spec.rb index 730b8e9..5073f2a 100644 --- a/spec/cc/engine/csslint_spec.rb +++ b/spec/cc/engine/csslint_spec.rb @@ -5,24 +5,43 @@ module CC module Engine describe CSSlint do let(:code) { Dir.mktmpdir } - let(:lint) { CSSlint.new(directory: code, io: nil, engine_config: {}) } - let(:content) { '#id { color: red; }' } + let(:engine_config) { {} } + let(:lint) do + CSSlint.new(directory: code, io: nil, engine_config: engine_config) + end + let(:id_selector_content) { '#id { color: red; }' } describe '#run' do it 'analyzes *.css files' do - create_source_file('foo.css', content) + create_source_file('foo.css', id_selector_content) expect{ lint.run }.to output(/Don't use IDs in selectors./).to_stdout end it "doesn't analyze *.scss files" do - create_source_file('foo.scss', content) + create_source_file('foo.scss', id_selector_content) expect{ lint.run }.to_not output.to_stdout end - def create_source_file(path, content) - File.write(File.join(code, path), content) + describe "with exclude_paths" do + let(:engine_config) { {"exclude_paths" => %w(excluded.css)} } + + before do + create_source_file("not_excluded.css", "p { margin: 5px }") + create_source_file("excluded.css", id_selector_content) + end + + it "excludes all matching paths" do + expect{ lint.run }.not_to \ + output(/Don't use IDs in selectors./).to_stdout + end end end + + def create_source_file(path, content) + abs_path = File.join(code, path) + FileUtils.mkdir_p(File.dirname(abs_path)) + File.write(abs_path, content) + end end end end From 0ec86aa0646cf11e39e3bf25307a904bd90070bc Mon Sep 17 00:00:00 2001 From: Francis Hwang Date: Wed, 16 Sep 2015 14:22:33 -0400 Subject: [PATCH 12/46] Support either include_paths or exclude_paths. --- lib/cc/engine/csslint.rb | 27 +++++++++++++++++++----- spec/cc/engine/csslint_spec.rb | 38 ++++++++++++++++++++++++++++++++++ 2 files changed, 60 insertions(+), 5 deletions(-) diff --git a/lib/cc/engine/csslint.rb b/lib/cc/engine/csslint.rb index 0eecfc4..46d68df 100644 --- a/lib/cc/engine/csslint.rb +++ b/lib/cc/engine/csslint.rb @@ -48,14 +48,31 @@ def results @results ||= Nokogiri::XML(csslint_xml) end + def build_files_with_exclusions(exclusions) + files = Dir.glob("**/*.css") + files.reject { |f| exclusions.include?(f) } + end + + def build_files_with_inclusions(inclusions) + inclusions.map do |include_path| + if include_path =~ %r{/$} + Dir.glob("#{include_path}/**/*.css") + else + include_path + end + end.flatten + end + def csslint_xml - exclusions = @engine_config['exclude_paths'] || [] - final_files = files.reject { |f| exclusions.include?(f) } - `csslint --format=checkstyle-xml #{final_files.join(" ")}` + `csslint --format=checkstyle-xml #{files_to_inspect.join(" ")}` end - def files - Dir.glob("**/*.css") + def files_to_inspect + if includes = @engine_config["include_paths"] + build_files_with_inclusions(includes) + else + build_files_with_exclusions(@engine_config["exclude_paths"] || []) + end end end end diff --git a/spec/cc/engine/csslint_spec.rb b/spec/cc/engine/csslint_spec.rb index 5073f2a..7daee33 100644 --- a/spec/cc/engine/csslint_spec.rb +++ b/spec/cc/engine/csslint_spec.rb @@ -35,6 +35,44 @@ module Engine output(/Don't use IDs in selectors./).to_stdout end end + + describe "with include_paths" do + let(:engine_config) { + {"include_paths" => %w(included.css included_dir/)} + } + + before do + create_source_file("included.css", id_selector_content) + create_source_file( + "included_dir/file.css", "p { color: blue !important; }" + ) + create_source_file( + "included_dir/sub/sub/subdir/file.css", "img { }" + ) + create_source_file("not_included.css", "a { outline: none; }") + end + + it "includes all mentioned files" do + expect{ lint.run }.to \ + output(/Don't use IDs in selectors./).to_stdout + end + + it "expands directories" do + expect{ lint.run }.to output(/Use of !important/).to_stdout + expect{ lint.run }.to output(/Rule is empty/).to_stdout + end + + it "excludes any unmentioned files" do + expect{ lint.run }.not_to \ + output(/Outlines should only be modified using :focus/).to_stdout + end + + it "shouldn't call a top-level Dir.glob ever" do + expect(Dir).not_to receive(:glob).with("**/*.css") + expect{ lint.run }.to \ + output(/Don't use IDs in selectors./).to_stdout + end + end end def create_source_file(path, content) From 462eeee764b2692c6cf03e4e2155868bc413ad5f Mon Sep 17 00:00:00 2001 From: Francis Hwang Date: Wed, 16 Sep 2015 15:06:51 -0400 Subject: [PATCH 13/46] Change CodeClimate and Rubocop config. --- .codeclimate.yml | 2 ++ .rubocop.yml | 41 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 43 insertions(+) create mode 100644 .rubocop.yml diff --git a/.codeclimate.yml b/.codeclimate.yml index b1b2114..1b449f2 100644 --- a/.codeclimate.yml +++ b/.codeclimate.yml @@ -4,3 +4,5 @@ engines: ratings: paths: - "**.rb" +exclude_paths: + - spec/**/* diff --git a/.rubocop.yml b/.rubocop.yml new file mode 100644 index 0000000..6583489 --- /dev/null +++ b/.rubocop.yml @@ -0,0 +1,41 @@ +Style/StringLiterals: + Enabled: false + +Style/Documentation: + Enabled: false + +Metrics/LineLength: + Enabled: false + +Style/TrailingComma: + Enabled: false + +Style/FileName: + Exclude: + - 'bin/**/*' + +Style/ClassAndModuleChildren: + Exclude: + - 'spec/**/*' + +Metrics/ModuleLength: + Exclude: + - 'spec/**/*' + +Style/GuardClause: + Enabled: false + +Style/IfUnlessModifier: + Enabled: false + +Style/DotPosition: + Enabled: false + +Style/SignalException: + Enabled: false + +Metrics/AbcSize: + Enabled: false + +Rails/TimeZone: + Enabled: false From 7d9790daf3e60817c6250b0cdcbbd183ef78a625 Mon Sep 17 00:00:00 2001 From: Francis Hwang Date: Wed, 16 Sep 2015 15:22:18 -0400 Subject: [PATCH 14/46] Style fixes. --- lib/cc/engine/csslint.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/cc/engine/csslint.rb b/lib/cc/engine/csslint.rb index 46d68df..9517c96 100644 --- a/lib/cc/engine/csslint.rb +++ b/lib/cc/engine/csslint.rb @@ -68,8 +68,8 @@ def csslint_xml end def files_to_inspect - if includes = @engine_config["include_paths"] - build_files_with_inclusions(includes) + if @engine_config["include_paths"] + build_files_with_inclusions(@engine_config["include_paths"]) else build_files_with_exclusions(@engine_config["exclude_paths"] || []) end From d87cb0e9f696c6266e4f29bdaba7ae05b993f6d5 Mon Sep 17 00:00:00 2001 From: Will Fleming Date: Thu, 17 Sep 2015 14:25:57 -0400 Subject: [PATCH 15/46] 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 a40573ef9c086d051b067668a6af78a38f06b60f Mon Sep 17 00:00:00 2001 From: Francis Hwang Date: Thu, 17 Sep 2015 17:08:51 -0400 Subject: [PATCH 16/46] Strip out non-CSS files directly included in include_paths. --- lib/cc/engine/csslint.rb | 4 ++-- spec/cc/engine/csslint_spec.rb | 7 ++++++- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/lib/cc/engine/csslint.rb b/lib/cc/engine/csslint.rb index 9517c96..d89da9d 100644 --- a/lib/cc/engine/csslint.rb +++ b/lib/cc/engine/csslint.rb @@ -58,9 +58,9 @@ def build_files_with_inclusions(inclusions) if include_path =~ %r{/$} Dir.glob("#{include_path}/**/*.css") else - include_path + include_path if include_path =~ /\.css$/ end - end.flatten + end.flatten.compact end def csslint_xml diff --git a/spec/cc/engine/csslint_spec.rb b/spec/cc/engine/csslint_spec.rb index 7daee33..7a12bff 100644 --- a/spec/cc/engine/csslint_spec.rb +++ b/spec/cc/engine/csslint_spec.rb @@ -38,7 +38,7 @@ module Engine describe "with include_paths" do let(:engine_config) { - {"include_paths" => %w(included.css included_dir/)} + {"include_paths" => %w(included.css included_dir/ config.yml)} } before do @@ -49,6 +49,7 @@ module Engine create_source_file( "included_dir/sub/sub/subdir/file.css", "img { }" ) + create_source_file("config.yml", "foo:\n bar: \"baz\"") create_source_file("not_included.css", "a { outline: none; }") end @@ -72,6 +73,10 @@ module Engine expect{ lint.run }.to \ output(/Don't use IDs in selectors./).to_stdout end + + it "only includes CSS files, even when a non-CSS file is directly included" do + expect{ lint.run }.not_to output(/config.yml/).to_stdout + end end end From 9ba43737322aeb940b0309b5e50772a7a2227231 Mon Sep 17 00:00:00 2001 From: John Pignata Date: Fri, 6 Nov 2015 22:00:06 -0500 Subject: [PATCH 17/46] Push to new Docker registry --- circle.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/circle.yml b/circle.yml index 1e75781..dd640de 100644 --- a/circle.yml +++ b/circle.yml @@ -3,6 +3,9 @@ machine: - docker environment: CLOUDSDK_CORE_DISABLE_PROMPTS: 1 + CODECLIMATE_DOCKER_REGISTRY_HOSTNAME: registry.codeclimate.net + CODECLIMATE_DOCKER_REGISTRY_USERNAME: circleci + CODECLIMATE_DOCKER_REGISTRY_EMAIL: ops@codeclimate.com dependencies: override: @@ -23,4 +26,7 @@ deployment: - gcloud auth activate-service-account --key-file /tmp/gcloud_key.json - gcloud docker -a - docker push $registry_root/$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 $registry_root/$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 From ecc834d00aa0deea83d30cde10dee98984247fe1 Mon Sep 17 00:00:00 2001 From: patrick brisbin Date: Fri, 4 Dec 2015 17:15:10 -0500 Subject: [PATCH 18/46] Track latest codeclimate/csslint SHA Brings support for --exclude-exts - https://github.com/codeclimate/csslint/compare/7a3a6be...27a21742 --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 5382140..eb18af3 100644 --- a/Dockerfile +++ b/Dockerfile @@ -8,7 +8,7 @@ RUN apk --update add nodejs git ruby ruby-dev ruby-bundler less ruby-nokogiri bu bundle install -j 4 && \ apk del build-base && rm -fr /usr/share/ri -RUN npm install -g codeclimate/csslint.git#7a3a6be +RUN npm install -g codeclimate/csslint.git#27a21742 RUN adduser -u 9000 -D app USER app From b11a84e81575b446103e2ecff58b59d77ada4e68 Mon Sep 17 00:00:00 2001 From: patrick brisbin Date: Mon, 7 Dec 2015 14:15:34 -0500 Subject: [PATCH 19/46] Add Rakefile --- Rakefile | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 Rakefile diff --git a/Rakefile b/Rakefile new file mode 100644 index 0000000..8281788 --- /dev/null +++ b/Rakefile @@ -0,0 +1,5 @@ +require "rspec/core/rake_task" + +RSpec::Core::RakeTask.new(:spec) + +task default: :spec From e7aff4f49f5d06bf64fe6f58ef64beb6ade968dc Mon Sep 17 00:00:00 2001 From: patrick brisbin Date: Mon, 7 Dec 2015 14:15:52 -0500 Subject: [PATCH 20/46] Don't report all errors on each file analyzed It's likely the original developer assumed that the pattern given to Node#xpath is treated relative to the node on which it's called. That doesn't seem to be the case. The XPath "//error" finds ALL error nodes across the entire document, even when invoked as file.xpath("//error"). This causes them all to be reported on every file analyzed. Besides being extremely non-sensical, this can also be a performance nightmare. To correct this, we instead use Node#children to get only the currently processing file's errors. This required replacing some hash accesses with methods and the chaining of #value on attributes. --- lib/cc/engine/csslint.rb | 18 +++++++++++------- spec/cc/engine/csslint_spec.rb | 6 ++++++ 2 files changed, 17 insertions(+), 7 deletions(-) diff --git a/lib/cc/engine/csslint.rb b/lib/cc/engine/csslint.rb index d89da9d..0614c7a 100644 --- a/lib/cc/engine/csslint.rb +++ b/lib/cc/engine/csslint.rb @@ -14,23 +14,27 @@ def run Dir.chdir(@directory) do results.xpath('//file').each do |file| path = file['name'].sub(/\A#{@directory}\//, '') - file.xpath('//error').each do |lint| + file.children.each do |node| + next unless node.name == "error" + + lint = node.attributes + issue = { type: "issue", - check_name: lint["source"], - description: lint["message"], + check_name: lint["source"].value, + description: lint["message"].value, categories: ["Style"], remediation_points: 500, location: { path: path, positions: { begin: { - line: lint["line"].to_i, - column: lint["column"].to_i + line: lint["line"].value.to_i, + column: lint["column"].value.to_i }, end: { - line: lint["line"].to_i, - column: lint["column"].to_i + line: lint["line"].value.to_i, + column: lint["column"].value.to_i } } } diff --git a/spec/cc/engine/csslint_spec.rb b/spec/cc/engine/csslint_spec.rb index 7a12bff..e1fc37b 100644 --- a/spec/cc/engine/csslint_spec.rb +++ b/spec/cc/engine/csslint_spec.rb @@ -22,6 +22,12 @@ module Engine expect{ lint.run }.to_not output.to_stdout end + it "only reports issues in the file where they're present" do + create_source_file('bad.css', id_selector_content) + create_source_file('good.css', '.foo { margin: 0 }') + expect{ lint.run }.not_to output(/good\.css/).to_stdout + end + describe "with exclude_paths" do let(:engine_config) { {"exclude_paths" => %w(excluded.css)} } From a5d4f2ed1d2468f75d568668f7e9b2f1ce5c5d64 Mon Sep 17 00:00:00 2001 From: patrick brisbin Date: Mon, 7 Dec 2015 15:17:30 -0500 Subject: [PATCH 21/46] Add slack notification --- circle.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/circle.yml b/circle.yml index dd640de..782f888 100644 --- a/circle.yml +++ b/circle.yml @@ -30,3 +30,6 @@ deployment: - docker tag $registry_root/$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: + - url: https://cc-slack-proxy.herokuapp.com/circle From 9da9360bfe7b07579ef364455eb0c2559b0602a5 Mon Sep 17 00:00:00 2001 From: patrick brisbin Date: Mon, 7 Dec 2015 16:29:44 -0500 Subject: [PATCH 22/46] Raise default remediation, categorize checks Categorize checks loosely based on the CSSLint wiki[1]. The most obvious departure was categorizing Performance rules as Bug Risk checks. 1: https://github.com/CSSLint/csslint/wiki/Rules Default remediation was at an abysmal 500, upped it to the "trivial" baseline for now and built a small class allowing us to easily override these later. I chose keyword arguments in the CheckDetails class so we can easily create an override object for one, the other, or both of categories and remediation points. --- lib/cc/engine/csslint.rb | 10 ++-- lib/cc/engine/csslint/check_details.rb | 55 ++++++++++++++++++++ spec/cc/engine/csslint/check_details_spec.rb | 21 ++++++++ spec/cc/engine/csslint_spec.rb | 3 +- spec/spec_helper.rb | 2 + 5 files changed, 86 insertions(+), 5 deletions(-) create mode 100644 lib/cc/engine/csslint/check_details.rb create mode 100644 spec/cc/engine/csslint/check_details_spec.rb create mode 100644 spec/spec_helper.rb diff --git a/lib/cc/engine/csslint.rb b/lib/cc/engine/csslint.rb index 0614c7a..7fa9670 100644 --- a/lib/cc/engine/csslint.rb +++ b/lib/cc/engine/csslint.rb @@ -4,6 +4,8 @@ module CC module Engine class CSSlint + autoload :CheckDetails, "cc/engine/csslint/check_details" + def initialize(directory: , io: , engine_config: ) @directory = directory @engine_config = engine_config @@ -18,13 +20,15 @@ def run next unless node.name == "error" lint = node.attributes + check_name = lint["source"].value + check_details = CheckDetails.fetch(check_name) issue = { type: "issue", - check_name: lint["source"].value, + check_name: check_name, description: lint["message"].value, - categories: ["Style"], - remediation_points: 500, + categories: check_details.categories, + remediation_points: check_details.remediation_points, location: { path: path, positions: { diff --git a/lib/cc/engine/csslint/check_details.rb b/lib/cc/engine/csslint/check_details.rb new file mode 100644 index 0000000..24768a0 --- /dev/null +++ b/lib/cc/engine/csslint/check_details.rb @@ -0,0 +1,55 @@ +module CC + module Engine + class CSSlint + # https://github.com/CSSLint/csslint/wiki/Rules + class CheckDetails + DEFAULT_CATEGORY = "Style".freeze + DEFAULT_REMEDIATION_POINTS = 50_000.freeze + + attr_reader :categories, :remediation_points + + def self.all + @all ||= { + "net.csslint.Adjoiningclasses" => new(categories: "Compatability"), + "net.csslint.Boxmodel" => new(categories: "Bug Risk"), + "net.csslint.Boxsizing" => new(categories: "Compatability"), + "net.csslint.Bulletprooffontface" => new(categories: "Compatability"), + "net.csslint.Compatiblevendorprefixes" => new(categories: "Compatability"), + "net.csslint.Displaypropertygrouping" => new(categories: "Bug Risk"), + "net.csslint.Duplicatebackgroundimages" => new(categories: "Bug Risk"), + "net.csslint.Duplicateproperties" => new(categories: "Bug Risk"), + "net.csslint.Emptyrules" => new(categories: "Bug Risk"), + "net.csslint.Fallbackcolors" => new(categories: "Compatability"), + "net.csslint.Fontfaces" => new(categories: "Bug Risk"), + "net.csslint.Gradients" => new(categories: "Compatability"), + "net.csslint.Import" => new(categories: "Bug Risk"), + "net.csslint.Knownproperties" => new(categories: "Bug Risk"), + "net.csslint.Overqualifiedelements" => new(categories: "Bug Risk"), + "net.csslint.Regexselectors" => new(categories: "Bug Risk"), + "net.csslint.Shorthand" => new(categories: "Bug Risk"), + "net.csslint.Starpropertyhack" => new(categories: "Compatability"), + "net.csslint.Textindent" => new(categories: "Compatability"), + "net.csslint.Underscorepropertyhack" => new(categories: "Compatability"), + "net.csslint.Uniqueheadings" => new(categories: "Duplication"), + "net.csslint.Universalselector" => new(categories: "Bug Risk"), + "net.csslint.Unqualifiedattributes" => new(categories: "Bug Risk"), + "net.csslint.Vendorprefix" => new(categories: "Compatability"), + "net.csslint.Zerounits" => new(categories: "Bug Risk"), + } + end + + def self.fetch(check_name) + all.fetch(check_name) { new } + end + + def initialize( + categories: DEFAULT_CATEGORY, + remediation_points: DEFAULT_REMEDIATION_POINTS + ) + @categories = Array(categories) + @remediation_points = remediation_points + end + end + end + end +end diff --git a/spec/cc/engine/csslint/check_details_spec.rb b/spec/cc/engine/csslint/check_details_spec.rb new file mode 100644 index 0000000..cfd8894 --- /dev/null +++ b/spec/cc/engine/csslint/check_details_spec.rb @@ -0,0 +1,21 @@ +require "spec_helper" + +class CC::Engine::CSSlint + describe CheckDetails do + describe ".fetch" do + it "returns details for customized checks" do + details = CheckDetails.fetch("net.csslint.Import") + + expect(details.categories).to eq ["Bug Risk"] + expect(details.remediation_points).to eq 50_000 + end + + it "returns defaults for unknown checks" do + details = CheckDetails.fetch("made-up") + + expect(details.categories).to eq ["Style"] + expect(details.remediation_points).to eq 50_000 + end + end + end +end diff --git a/spec/cc/engine/csslint_spec.rb b/spec/cc/engine/csslint_spec.rb index e1fc37b..d6f72b2 100644 --- a/spec/cc/engine/csslint_spec.rb +++ b/spec/cc/engine/csslint_spec.rb @@ -1,5 +1,4 @@ -require 'cc/engine/csslint' -require 'tmpdir' +require "spec_helper" module CC module Engine diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb new file mode 100644 index 0000000..2be99e0 --- /dev/null +++ b/spec/spec_helper.rb @@ -0,0 +1,2 @@ +require "cc/engine/csslint" +require "tmpdir" From e17113a2620d2492a6037723acb5ee5ceccc18c2 Mon Sep 17 00:00:00 2001 From: patrick brisbin Date: Mon, 7 Dec 2015 17:39:48 -0500 Subject: [PATCH 23/46] Use a constant --- lib/cc/engine/csslint/check_details.rb | 62 +++++++++++++------------- 1 file changed, 30 insertions(+), 32 deletions(-) diff --git a/lib/cc/engine/csslint/check_details.rb b/lib/cc/engine/csslint/check_details.rb index 24768a0..ea2f5e3 100644 --- a/lib/cc/engine/csslint/check_details.rb +++ b/lib/cc/engine/csslint/check_details.rb @@ -1,45 +1,43 @@ module CC module Engine class CSSlint - # https://github.com/CSSLint/csslint/wiki/Rules class CheckDetails + ALL_RULES = { + # https://github.com/CSSLint/csslint/wiki/Rules + "net.csslint.Adjoiningclasses" => { categories: "Compatability" }, + "net.csslint.Boxmodel" => { categories: "Bug Risk" }, + "net.csslint.Boxsizing" => { categories: "Compatability" }, + "net.csslint.Bulletprooffontface" => { categories: "Compatability" }, + "net.csslint.Compatiblevendorprefixes" => { categories: "Compatability" }, + "net.csslint.Displaypropertygrouping" => { categories: "Bug Risk" }, + "net.csslint.Duplicatebackgroundimages" => { categories: "Bug Risk" }, + "net.csslint.Duplicateproperties" => { categories: "Bug Risk" }, + "net.csslint.Emptyrules" => { categories: "Bug Risk" }, + "net.csslint.Fallbackcolors" => { categories: "Compatability" }, + "net.csslint.Fontfaces" => { categories: "Bug Risk" }, + "net.csslint.Gradients" => { categories: "Compatability" }, + "net.csslint.Import" => { categories: "Bug Risk" }, + "net.csslint.Knownproperties" => { categories: "Bug Risk" }, + "net.csslint.Overqualifiedelements" => { categories: "Bug Risk" }, + "net.csslint.Regexselectors" => { categories: "Bug Risk" }, + "net.csslint.Shorthand" => { categories: "Bug Risk" }, + "net.csslint.Starpropertyhack" => { categories: "Compatability" }, + "net.csslint.Textindent" => { categories: "Compatability" }, + "net.csslint.Underscorepropertyhack" => { categories: "Compatability" }, + "net.csslint.Uniqueheadings" => { categories: "Duplication" }, + "net.csslint.Universalselector" => { categories: "Bug Risk" }, + "net.csslint.Unqualifiedattributes" => { categories: "Bug Risk" }, + "net.csslint.Vendorprefix" => { categories: "Compatability" }, + "net.csslint.Zerounits" => { categories: "Bug Risk" }, + }.freeze + DEFAULT_CATEGORY = "Style".freeze DEFAULT_REMEDIATION_POINTS = 50_000.freeze attr_reader :categories, :remediation_points - def self.all - @all ||= { - "net.csslint.Adjoiningclasses" => new(categories: "Compatability"), - "net.csslint.Boxmodel" => new(categories: "Bug Risk"), - "net.csslint.Boxsizing" => new(categories: "Compatability"), - "net.csslint.Bulletprooffontface" => new(categories: "Compatability"), - "net.csslint.Compatiblevendorprefixes" => new(categories: "Compatability"), - "net.csslint.Displaypropertygrouping" => new(categories: "Bug Risk"), - "net.csslint.Duplicatebackgroundimages" => new(categories: "Bug Risk"), - "net.csslint.Duplicateproperties" => new(categories: "Bug Risk"), - "net.csslint.Emptyrules" => new(categories: "Bug Risk"), - "net.csslint.Fallbackcolors" => new(categories: "Compatability"), - "net.csslint.Fontfaces" => new(categories: "Bug Risk"), - "net.csslint.Gradients" => new(categories: "Compatability"), - "net.csslint.Import" => new(categories: "Bug Risk"), - "net.csslint.Knownproperties" => new(categories: "Bug Risk"), - "net.csslint.Overqualifiedelements" => new(categories: "Bug Risk"), - "net.csslint.Regexselectors" => new(categories: "Bug Risk"), - "net.csslint.Shorthand" => new(categories: "Bug Risk"), - "net.csslint.Starpropertyhack" => new(categories: "Compatability"), - "net.csslint.Textindent" => new(categories: "Compatability"), - "net.csslint.Underscorepropertyhack" => new(categories: "Compatability"), - "net.csslint.Uniqueheadings" => new(categories: "Duplication"), - "net.csslint.Universalselector" => new(categories: "Bug Risk"), - "net.csslint.Unqualifiedattributes" => new(categories: "Bug Risk"), - "net.csslint.Vendorprefix" => new(categories: "Compatability"), - "net.csslint.Zerounits" => new(categories: "Bug Risk"), - } - end - def self.fetch(check_name) - all.fetch(check_name) { new } + new(ALL_RULES.fetch(check_name, {})) end def initialize( From f3c5f5153fac8681da215ff3c3ed7a4bc9a0e079 Mon Sep 17 00:00:00 2001 From: patrick brisbin Date: Mon, 7 Dec 2015 18:36:18 -0500 Subject: [PATCH 24/46] Fix check-details keys I originally thought source (which we use as check-name) was a munged version of the rule-id[1]: id: import => source: net.csslint.Import It's actually a munged versions of rule-name[2]: name: Disallow @import => net.css.lint.Disallow@import This commit corrects the CheckDetails map keys to use what is actually emitted as source. 1: https://github.com/CSSLint/csslint/blob/f69cf12e23ca95cfd9e78a4a5d83fdccb71bb8be/src/rules/import.js#L8 2: https://github.com/CSSLint/csslint/blob/f69cf12e23ca95cfd9e78a4a5d83fdccb71bb8be/src/rules/import.js#L9 --- lib/cc/engine/csslint/check_details.rb | 50 ++++++++++---------- spec/cc/engine/csslint/check_details_spec.rb | 2 +- 2 files changed, 26 insertions(+), 26 deletions(-) diff --git a/lib/cc/engine/csslint/check_details.rb b/lib/cc/engine/csslint/check_details.rb index ea2f5e3..3deef3f 100644 --- a/lib/cc/engine/csslint/check_details.rb +++ b/lib/cc/engine/csslint/check_details.rb @@ -4,31 +4,31 @@ class CSSlint class CheckDetails ALL_RULES = { # https://github.com/CSSLint/csslint/wiki/Rules - "net.csslint.Adjoiningclasses" => { categories: "Compatability" }, - "net.csslint.Boxmodel" => { categories: "Bug Risk" }, - "net.csslint.Boxsizing" => { categories: "Compatability" }, - "net.csslint.Bulletprooffontface" => { categories: "Compatability" }, - "net.csslint.Compatiblevendorprefixes" => { categories: "Compatability" }, - "net.csslint.Displaypropertygrouping" => { categories: "Bug Risk" }, - "net.csslint.Duplicatebackgroundimages" => { categories: "Bug Risk" }, - "net.csslint.Duplicateproperties" => { categories: "Bug Risk" }, - "net.csslint.Emptyrules" => { categories: "Bug Risk" }, - "net.csslint.Fallbackcolors" => { categories: "Compatability" }, - "net.csslint.Fontfaces" => { categories: "Bug Risk" }, - "net.csslint.Gradients" => { categories: "Compatability" }, - "net.csslint.Import" => { categories: "Bug Risk" }, - "net.csslint.Knownproperties" => { categories: "Bug Risk" }, - "net.csslint.Overqualifiedelements" => { categories: "Bug Risk" }, - "net.csslint.Regexselectors" => { categories: "Bug Risk" }, - "net.csslint.Shorthand" => { categories: "Bug Risk" }, - "net.csslint.Starpropertyhack" => { categories: "Compatability" }, - "net.csslint.Textindent" => { categories: "Compatability" }, - "net.csslint.Underscorepropertyhack" => { categories: "Compatability" }, - "net.csslint.Uniqueheadings" => { categories: "Duplication" }, - "net.csslint.Universalselector" => { categories: "Bug Risk" }, - "net.csslint.Unqualifiedattributes" => { categories: "Bug Risk" }, - "net.csslint.Vendorprefix" => { categories: "Compatability" }, - "net.csslint.Zerounits" => { categories: "Bug Risk" }, + "net.csslint.Bewareofbrokenboxsize" => { categories: "Bug Risk" }, + "net.csslint.Disallow@import" => { categories: "Bug Risk" }, + "net.csslint.Disallowadjoiningclasses" => { categories: "Compatability" }, + "net.csslint.Disallowduplicatebackgroundimages" => { categories: "Bug Risk" }, + "net.csslint.Disallowduplicateproperties" => { categories: "Bug Risk" }, + "net.csslint.Disallowemptyrules" => { categories: "Bug Risk" }, + "net.csslint.Disallownegativetext-indent" => { categories: "Compatability" }, + "net.csslint.Disallowoverqualifiedelements" => { categories: "Bug Risk" }, + "net.csslint.Disallowpropertieswithanunderscoreprefix" => { categories: "Compatability" }, + "net.csslint.Disallowpropertieswithastarprefix" => { categories: "Compatability" }, + "net.csslint.Disallowselectorsthatlooklikeregexs" => { categories: "Bug Risk" }, + "net.csslint.Disallowunitsfor0values" => { categories: "Bug Risk" }, + "net.csslint.Disallowuniversalselector" => { categories: "Bug Risk" }, + "net.csslint.Disallowunqualifiedattributeselectors" => { categories: "Bug Risk" }, + "net.csslint.Disallowuseofbox-sizing" => { categories: "Compatability" }, + "net.csslint.Don'tusetoomanywebfonts" => { categories: "Bug Risk" }, + "net.csslint.Headingsshouldonlybedefinedonce" => { categories: "Duplication" }, + "net.csslint.Requireallgradientdefinitions" => { categories: "Compatability" }, + "net.csslint.Requirecompatiblevendorprefixes" => { categories: "Compatability" }, + "net.csslint.Requirefallbackcolors" => { categories: "Compatability" }, + "net.csslint.Requirepropertiesappropriatefordisplay" => { categories: "Bug Risk" }, + "net.csslint.Requireshorthandproperties" => { categories: "Bug Risk" }, + "net.csslint.Requirestandardpropertywithvendorprefix" => { categories: "Compatability" }, + "net.csslint.Requireuseofknownproperties" => { categories: "Bug Risk" }, + "net.csslint.Usethebulletproof@font-facesyntax" => { categories: "Compatability" }, }.freeze DEFAULT_CATEGORY = "Style".freeze diff --git a/spec/cc/engine/csslint/check_details_spec.rb b/spec/cc/engine/csslint/check_details_spec.rb index cfd8894..2892800 100644 --- a/spec/cc/engine/csslint/check_details_spec.rb +++ b/spec/cc/engine/csslint/check_details_spec.rb @@ -4,7 +4,7 @@ class CC::Engine::CSSlint describe CheckDetails do describe ".fetch" do it "returns details for customized checks" do - details = CheckDetails.fetch("net.csslint.Import") + details = CheckDetails.fetch("net.csslint.Disallow@import") expect(details.categories).to eq ["Bug Risk"] expect(details.remediation_points).to eq 50_000 From 0eb5e9420bd670386becb72335abf472e26ddcc5 Mon Sep 17 00:00:00 2001 From: GordonDiggs Date: Mon, 7 Dec 2015 19:05:59 -0500 Subject: [PATCH 25/46] Fix spelling of category --- lib/cc/engine/csslint/check_details.rb | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/lib/cc/engine/csslint/check_details.rb b/lib/cc/engine/csslint/check_details.rb index 3deef3f..8da929d 100644 --- a/lib/cc/engine/csslint/check_details.rb +++ b/lib/cc/engine/csslint/check_details.rb @@ -6,29 +6,29 @@ class CheckDetails # https://github.com/CSSLint/csslint/wiki/Rules "net.csslint.Bewareofbrokenboxsize" => { categories: "Bug Risk" }, "net.csslint.Disallow@import" => { categories: "Bug Risk" }, - "net.csslint.Disallowadjoiningclasses" => { categories: "Compatability" }, + "net.csslint.Disallowadjoiningclasses" => { categories: "Compatibility" }, "net.csslint.Disallowduplicatebackgroundimages" => { categories: "Bug Risk" }, "net.csslint.Disallowduplicateproperties" => { categories: "Bug Risk" }, "net.csslint.Disallowemptyrules" => { categories: "Bug Risk" }, - "net.csslint.Disallownegativetext-indent" => { categories: "Compatability" }, + "net.csslint.Disallownegativetext-indent" => { categories: "Compatibility" }, "net.csslint.Disallowoverqualifiedelements" => { categories: "Bug Risk" }, - "net.csslint.Disallowpropertieswithanunderscoreprefix" => { categories: "Compatability" }, - "net.csslint.Disallowpropertieswithastarprefix" => { categories: "Compatability" }, + "net.csslint.Disallowpropertieswithanunderscoreprefix" => { categories: "Compatibility" }, + "net.csslint.Disallowpropertieswithastarprefix" => { categories: "Compatibility" }, "net.csslint.Disallowselectorsthatlooklikeregexs" => { categories: "Bug Risk" }, "net.csslint.Disallowunitsfor0values" => { categories: "Bug Risk" }, "net.csslint.Disallowuniversalselector" => { categories: "Bug Risk" }, "net.csslint.Disallowunqualifiedattributeselectors" => { categories: "Bug Risk" }, - "net.csslint.Disallowuseofbox-sizing" => { categories: "Compatability" }, + "net.csslint.Disallowuseofbox-sizing" => { categories: "Compatibility" }, "net.csslint.Don'tusetoomanywebfonts" => { categories: "Bug Risk" }, "net.csslint.Headingsshouldonlybedefinedonce" => { categories: "Duplication" }, - "net.csslint.Requireallgradientdefinitions" => { categories: "Compatability" }, - "net.csslint.Requirecompatiblevendorprefixes" => { categories: "Compatability" }, - "net.csslint.Requirefallbackcolors" => { categories: "Compatability" }, + "net.csslint.Requireallgradientdefinitions" => { categories: "Compatibility" }, + "net.csslint.Requirecompatiblevendorprefixes" => { categories: "Compatibility" }, + "net.csslint.Requirefallbackcolors" => { categories: "Compatibility" }, "net.csslint.Requirepropertiesappropriatefordisplay" => { categories: "Bug Risk" }, "net.csslint.Requireshorthandproperties" => { categories: "Bug Risk" }, - "net.csslint.Requirestandardpropertywithvendorprefix" => { categories: "Compatability" }, + "net.csslint.Requirestandardpropertywithvendorprefix" => { categories: "Compatibility" }, "net.csslint.Requireuseofknownproperties" => { categories: "Bug Risk" }, - "net.csslint.Usethebulletproof@font-facesyntax" => { categories: "Compatability" }, + "net.csslint.Usethebulletproof@font-facesyntax" => { categories: "Compatibility" }, }.freeze DEFAULT_CATEGORY = "Style".freeze From 6774accea3ae7d8929228cf62f34d293c464e3cd Mon Sep 17 00:00:00 2001 From: patrick brisbin Date: Tue, 8 Dec 2015 16:25:12 -0500 Subject: [PATCH 26/46] Bump csslint Adds "identifier" to checkstyle output, which we can use to generate a more friendly check name on our side. https://github.com/codeclimate/csslint/commit/e8b88550e8d4ed85d60c0d89708174eb1dc96c39 --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index eb18af3..da11f4a 100644 --- a/Dockerfile +++ b/Dockerfile @@ -8,7 +8,7 @@ RUN apk --update add nodejs git ruby ruby-dev ruby-bundler less ruby-nokogiri bu bundle install -j 4 && \ apk del build-base && rm -fr /usr/share/ri -RUN npm install -g codeclimate/csslint.git#27a21742 +RUN npm install -g codeclimate/csslint.git#43d315c2 RUN adduser -u 9000 -D app USER app From 97e86b7ba775c7064ca2fa7db3830dfec7b91759 Mon Sep 17 00:00:00 2001 From: patrick brisbin Date: Tue, 8 Dec 2015 16:45:59 -0500 Subject: [PATCH 27/46] Improve build process, run tests via docker --- .dockerignore | 2 ++ Makefile | 7 +++++++ circle.yml | 18 +++++++----------- 3 files changed, 16 insertions(+), 11 deletions(-) create mode 100644 .dockerignore create mode 100644 Makefile diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..cf6ce25 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,2 @@ +.git +Makefile diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..f11fb3d --- /dev/null +++ b/Makefile @@ -0,0 +1,7 @@ +.PHONY: test + +image: + docker build -t codeclimate/codeclimate-csslint . + +test: image + docker run --rm codeclimate/codeclimate-csslint rake diff --git a/circle.yml b/circle.yml index 782f888..403a8a6 100644 --- a/circle.yml +++ b/circle.yml @@ -7,25 +7,21 @@ machine: CODECLIMATE_DOCKER_REGISTRY_USERNAME: circleci CODECLIMATE_DOCKER_REGISTRY_EMAIL: ops@codeclimate.com -dependencies: - override: - - echo $gcloud_json_key_base64 | sed 's/ //g' | base64 -d > /tmp/gcloud_key.json - - curl https://sdk.cloud.google.com | bash - - bundle install - - npm install -g codeclimate/csslint.git#7a3a6be - test: override: - - bundle exec rspec spec - - docker build -t=$registry_root/$CIRCLE_PROJECT_REPONAME:b$CIRCLE_BUILD_NUM . + - make test deployment: registry: branch: master commands: + # GCR for .com + - echo $gcloud_json_key_base64 | sed 's/ //g' | base64 -d > /tmp/gcloud_key.json + - docker tag codeclimate/codeclimate-csslint $registry_root/$CIRCLE_PROJECT_REPONAME:b$CIRCLE_BUILD_NUM - gcloud auth activate-service-account --key-file /tmp/gcloud_key.json - - gcloud docker -a - - docker push $registry_root/$CIRCLE_PROJECT_REPONAME:b$CIRCLE_BUILD_NUM + - gcloud docker push $registry_root/$CIRCLE_PROJECT_REPONAME:b$CIRCLE_BUILD_NUM + + # registry.codeclimate.net for CC:E - docker login --username=$CODECLIMATE_DOCKER_REGISTRY_USERNAME --password=$CODECLIMATE_DOCKER_REGISTRY_PASSWORD --email=$CODECLIMATE_DOCKER_REGISTRY_EMAIL $CODECLIMATE_DOCKER_REGISTRY_HOSTNAME - docker tag $registry_root/$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 From c8fd68fa5c226ce38637410339884ec86dd32072 Mon Sep 17 00:00:00 2001 From: patrick brisbin Date: Tue, 8 Dec 2015 16:46:06 -0500 Subject: [PATCH 28/46] Use rule identifier as check name Instead of "net.csslint.Disallow@import" (source, which is a munged rule.name), just use "import" (rule.id). --- lib/cc/engine/csslint.rb | 2 +- lib/cc/engine/csslint/check_details.rb | 50 ++++++++++---------- spec/cc/engine/csslint/check_details_spec.rb | 2 +- 3 files changed, 27 insertions(+), 27 deletions(-) diff --git a/lib/cc/engine/csslint.rb b/lib/cc/engine/csslint.rb index 7fa9670..e1aa8b6 100644 --- a/lib/cc/engine/csslint.rb +++ b/lib/cc/engine/csslint.rb @@ -20,7 +20,7 @@ def run next unless node.name == "error" lint = node.attributes - check_name = lint["source"].value + check_name = lint["identifier"].value check_details = CheckDetails.fetch(check_name) issue = { diff --git a/lib/cc/engine/csslint/check_details.rb b/lib/cc/engine/csslint/check_details.rb index 8da929d..04133d5 100644 --- a/lib/cc/engine/csslint/check_details.rb +++ b/lib/cc/engine/csslint/check_details.rb @@ -4,31 +4,31 @@ class CSSlint class CheckDetails ALL_RULES = { # https://github.com/CSSLint/csslint/wiki/Rules - "net.csslint.Bewareofbrokenboxsize" => { categories: "Bug Risk" }, - "net.csslint.Disallow@import" => { categories: "Bug Risk" }, - "net.csslint.Disallowadjoiningclasses" => { categories: "Compatibility" }, - "net.csslint.Disallowduplicatebackgroundimages" => { categories: "Bug Risk" }, - "net.csslint.Disallowduplicateproperties" => { categories: "Bug Risk" }, - "net.csslint.Disallowemptyrules" => { categories: "Bug Risk" }, - "net.csslint.Disallownegativetext-indent" => { categories: "Compatibility" }, - "net.csslint.Disallowoverqualifiedelements" => { categories: "Bug Risk" }, - "net.csslint.Disallowpropertieswithanunderscoreprefix" => { categories: "Compatibility" }, - "net.csslint.Disallowpropertieswithastarprefix" => { categories: "Compatibility" }, - "net.csslint.Disallowselectorsthatlooklikeregexs" => { categories: "Bug Risk" }, - "net.csslint.Disallowunitsfor0values" => { categories: "Bug Risk" }, - "net.csslint.Disallowuniversalselector" => { categories: "Bug Risk" }, - "net.csslint.Disallowunqualifiedattributeselectors" => { categories: "Bug Risk" }, - "net.csslint.Disallowuseofbox-sizing" => { categories: "Compatibility" }, - "net.csslint.Don'tusetoomanywebfonts" => { categories: "Bug Risk" }, - "net.csslint.Headingsshouldonlybedefinedonce" => { categories: "Duplication" }, - "net.csslint.Requireallgradientdefinitions" => { categories: "Compatibility" }, - "net.csslint.Requirecompatiblevendorprefixes" => { categories: "Compatibility" }, - "net.csslint.Requirefallbackcolors" => { categories: "Compatibility" }, - "net.csslint.Requirepropertiesappropriatefordisplay" => { categories: "Bug Risk" }, - "net.csslint.Requireshorthandproperties" => { categories: "Bug Risk" }, - "net.csslint.Requirestandardpropertywithvendorprefix" => { categories: "Compatibility" }, - "net.csslint.Requireuseofknownproperties" => { categories: "Bug Risk" }, - "net.csslint.Usethebulletproof@font-facesyntax" => { categories: "Compatibility" }, + "adjoining-classes" => { categories: "Compatibility" }, + "box-model" => { categories: "Bug Risk" }, + "box-sizing" => { categories: "Compatibility" }, + "bulletproof-font-face" => { categories: "Compatibility" }, + "compatible-vendor-prefixes" => { categories: "Compatibility" }, + "display-property-grouping" => { categories: "Bug Risk" }, + "duplicate-background-images" => { categories: "Bug Risk" }, + "duplicate-properties" => { categories: "Bug Risk" }, + "empty-rules" => { categories: "Bug Risk" }, + "fallback-colors" => { categories: "Compatibility" }, + "font-faces" => { categories: "Bug Risk" }, + "gradients" => { categories: "Compatibility" }, + "import" => { categories: "Bug Risk" }, + "known-properties" => { categories: "Bug Risk" }, + "overqualified-elements" => { categories: "Bug Risk" }, + "regex-selectors" => { categories: "Bug Risk" }, + "shorthand" => { categories: "Bug Risk" }, + "star-property-hack" => { categories: "Compatibility" }, + "text-indent" => { categories: "Compatibility" }, + "underscore-property-hack" => { categories: "Compatibility" }, + "unique-headings" => { categories: "Duplication" }, + "universal-selector" => { categories: "Bug Risk" }, + "unqualified-attributes" => { categories: "Bug Risk" }, + "vendor-prefix" => { categories: "Compatibility" }, + "zero-units" => { categories: "Bug Risk" }, }.freeze DEFAULT_CATEGORY = "Style".freeze diff --git a/spec/cc/engine/csslint/check_details_spec.rb b/spec/cc/engine/csslint/check_details_spec.rb index 2892800..134dbb6 100644 --- a/spec/cc/engine/csslint/check_details_spec.rb +++ b/spec/cc/engine/csslint/check_details_spec.rb @@ -4,7 +4,7 @@ class CC::Engine::CSSlint describe CheckDetails do describe ".fetch" do it "returns details for customized checks" do - details = CheckDetails.fetch("net.csslint.Disallow@import") + details = CheckDetails.fetch("import") expect(details.categories).to eq ["Bug Risk"] expect(details.remediation_points).to eq 50_000 From d45e0f9cf12e238c3267279a7d4d12b8931d5522 Mon Sep 17 00:00:00 2001 From: patrick brisbin Date: Wed, 9 Dec 2015 13:33:43 -0500 Subject: [PATCH 29/46] Bump csslint This commit fixes the checkstyle-xml formatter to include an identifier attribute on all errors. File read errors were previously missed and caused a NoMethodError in the engine when an identifier attribute was used on these errors. The commit sha was also extracted to a variable in the Dockerfile and added as a full commit sha. This makes for easier editing (git log or the GitHub UI most often gives you full shas) and should keep the diff clearer that only the sha was changed. --- Dockerfile | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index da11f4a..92b149d 100644 --- a/Dockerfile +++ b/Dockerfile @@ -8,7 +8,8 @@ RUN apk --update add nodejs git ruby ruby-dev ruby-bundler less ruby-nokogiri bu bundle install -j 4 && \ apk del build-base && rm -fr /usr/share/ri -RUN npm install -g codeclimate/csslint.git#43d315c2 +ENV CSSLINT_SHA=0e7aeadd02f5babca5152993374043f9f736a116 +RUN npm install -g codeclimate/csslint.git#$CSSLINT_COMMIT RUN adduser -u 9000 -D app USER app From de0dde1d790200238d78bdfafee19255cd5ab2e7 Mon Sep 17 00:00:00 2001 From: Caio Fernando Bertoldi Paes de Andrade Date: Mon, 8 Feb 2016 16:00:52 -0200 Subject: [PATCH 30/46] Assert that expected attributes are present in XML Raise a meaningful MissingAttributesError if not. The exception also shows the raw XML node so the reason for the missing attribute can be determined and addressed. --- lib/cc/engine/csslint.rb | 57 ++++++++++++++++++---------------- spec/cc/engine/csslint_spec.rb | 5 +++ 2 files changed, 36 insertions(+), 26 deletions(-) diff --git a/lib/cc/engine/csslint.rb b/lib/cc/engine/csslint.rb index e1aa8b6..2b2d9fe 100644 --- a/lib/cc/engine/csslint.rb +++ b/lib/cc/engine/csslint.rb @@ -3,6 +3,8 @@ module CC module Engine + MissingAttributesError = Class.new(StandardError) + class CSSlint autoload :CheckDetails, "cc/engine/csslint/check_details" @@ -18,32 +20,7 @@ def run path = file['name'].sub(/\A#{@directory}\//, '') file.children.each do |node| next unless node.name == "error" - - lint = node.attributes - check_name = lint["identifier"].value - check_details = CheckDetails.fetch(check_name) - - issue = { - type: "issue", - check_name: check_name, - description: lint["message"].value, - categories: check_details.categories, - remediation_points: check_details.remediation_points, - location: { - path: path, - positions: { - begin: { - line: lint["line"].value.to_i, - column: lint["column"].value.to_i - }, - end: { - line: lint["line"].value.to_i, - column: lint["column"].value.to_i - } - } - } - } - + issue = create_issue(node, path) puts("#{issue.to_json}\0") end end @@ -52,6 +29,34 @@ def run private + def create_issue(node, path) + check_name = node.attributes.fetch("identifier").value + check_details = CheckDetails.fetch(check_name) + + { + type: "issue", + check_name: check_name, + description: node.attributes.fetch("message").value, + categories: check_details.categories, + remediation_points: check_details.remediation_points, + location: { + path: path, + positions: { + begin: { + line: node.attributes.fetch("line").value.to_i, + column: node.attributes.fetch("column").value.to_i + }, + end: { + line: node.attributes.fetch("line").value.to_i, + column: node.attributes.fetch("column").value.to_i + } + } + } + } + rescue KeyError => ex + raise MissingAttributesError, "#{ex.message} on XML '#{node}' when analyzing file '#{path}'" + end + def results @results ||= Nokogiri::XML(csslint_xml) end diff --git a/spec/cc/engine/csslint_spec.rb b/spec/cc/engine/csslint_spec.rb index d6f72b2..ec5e0a8 100644 --- a/spec/cc/engine/csslint_spec.rb +++ b/spec/cc/engine/csslint_spec.rb @@ -16,6 +16,11 @@ module Engine expect{ lint.run }.to output(/Don't use IDs in selectors./).to_stdout end + it 'fails on malformed file' do + create_source_file('foo.css', '�6�') + expect{ lint.run }.to raise_error(MissingAttributesError) + end + it "doesn't analyze *.scss files" do create_source_file('foo.scss', id_selector_content) expect{ lint.run }.to_not output.to_stdout From b43a484b7554bbf8563a188bb141843467caec24 Mon Sep 17 00:00:00 2001 From: patrick brisbin Date: Fri, 12 Feb 2016 09:28:12 -0500 Subject: [PATCH 31/46] Remove support for exclude_paths Refactor #files_to_inspect a little in the course of doing so. --- lib/cc/engine/csslint.rb | 26 +++++++------------------- spec/cc/engine/csslint_spec.rb | 15 +-------------- 2 files changed, 8 insertions(+), 33 deletions(-) diff --git a/lib/cc/engine/csslint.rb b/lib/cc/engine/csslint.rb index 2b2d9fe..0881d64 100644 --- a/lib/cc/engine/csslint.rb +++ b/lib/cc/engine/csslint.rb @@ -61,30 +61,18 @@ def results @results ||= Nokogiri::XML(csslint_xml) end - def build_files_with_exclusions(exclusions) - files = Dir.glob("**/*.css") - files.reject { |f| exclusions.include?(f) } - end - - def build_files_with_inclusions(inclusions) - inclusions.map do |include_path| - if include_path =~ %r{/$} - Dir.glob("#{include_path}/**/*.css") - else - include_path if include_path =~ /\.css$/ - end - end.flatten.compact - end - def csslint_xml `csslint --format=checkstyle-xml #{files_to_inspect.join(" ")}` end def files_to_inspect - if @engine_config["include_paths"] - build_files_with_inclusions(@engine_config["include_paths"]) - else - build_files_with_exclusions(@engine_config["exclude_paths"] || []) + include_paths = @engine_config["include_paths"] || ["./"] + include_paths.each_with_object([]) do |path, out| + if path.end_with?("/") + out.concat(Dir.glob("#{path}**/*.css")) + elsif path.end_with?(".css") + out << path + end end end end diff --git a/spec/cc/engine/csslint_spec.rb b/spec/cc/engine/csslint_spec.rb index ec5e0a8..356848c 100644 --- a/spec/cc/engine/csslint_spec.rb +++ b/spec/cc/engine/csslint_spec.rb @@ -32,20 +32,6 @@ module Engine expect{ lint.run }.not_to output(/good\.css/).to_stdout end - describe "with exclude_paths" do - let(:engine_config) { {"exclude_paths" => %w(excluded.css)} } - - before do - create_source_file("not_excluded.css", "p { margin: 5px }") - create_source_file("excluded.css", id_selector_content) - end - - it "excludes all matching paths" do - expect{ lint.run }.not_to \ - output(/Don't use IDs in selectors./).to_stdout - end - end - describe "with include_paths" do let(:engine_config) { {"include_paths" => %w(included.css included_dir/ config.yml)} @@ -79,6 +65,7 @@ module Engine end it "shouldn't call a top-level Dir.glob ever" do + allow(Dir).to receive(:glob).and_call_original expect(Dir).not_to receive(:glob).with("**/*.css") expect{ lint.run }.to \ output(/Don't use IDs in selectors./).to_stdout From 940a757b34a1b4d08367bc92fd9318445d1da009 Mon Sep 17 00:00:00 2001 From: patrick brisbin Date: Fri, 12 Feb 2016 09:29:35 -0500 Subject: [PATCH 32/46] Use shelljoin Ensures any file arguments with spaces are correctly handled. --- lib/cc/engine/csslint.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/cc/engine/csslint.rb b/lib/cc/engine/csslint.rb index 0881d64..cbbb3dd 100644 --- a/lib/cc/engine/csslint.rb +++ b/lib/cc/engine/csslint.rb @@ -62,7 +62,7 @@ def results end def csslint_xml - `csslint --format=checkstyle-xml #{files_to_inspect.join(" ")}` + `csslint --format=checkstyle-xml #{files_to_inspect.shelljoin}` end def files_to_inspect From 31681544dc7dffb344c3732ed31177e597445aa8 Mon Sep 17 00:00:00 2001 From: Will Fleming Date: Tue, 16 Feb 2016 10:25:51 -0500 Subject: [PATCH 33/46] require shellwords Needs to be explicitly required. --- lib/cc/engine/csslint.rb | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/cc/engine/csslint.rb b/lib/cc/engine/csslint.rb index cbbb3dd..bb25c85 100644 --- a/lib/cc/engine/csslint.rb +++ b/lib/cc/engine/csslint.rb @@ -1,5 +1,6 @@ -require 'nokogiri' -require 'json' +require "json" +require "nokogiri" +require "shellwords" module CC module Engine From 2118ad1e4ca7cc13131869be825650ea63c7821f Mon Sep 17 00:00:00 2001 From: Gordon Diggs Date: Mon, 22 Feb 2016 09:42:14 -0500 Subject: [PATCH 34/46] Remove CC registry --- circle.yml | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/circle.yml b/circle.yml index 403a8a6..f2c3c0b 100644 --- a/circle.yml +++ b/circle.yml @@ -3,9 +3,6 @@ machine: - docker environment: CLOUDSDK_CORE_DISABLE_PROMPTS: 1 - CODECLIMATE_DOCKER_REGISTRY_HOSTNAME: registry.codeclimate.net - CODECLIMATE_DOCKER_REGISTRY_USERNAME: circleci - CODECLIMATE_DOCKER_REGISTRY_EMAIL: ops@codeclimate.com test: override: @@ -15,17 +12,10 @@ deployment: registry: branch: master commands: - # GCR for .com - echo $gcloud_json_key_base64 | sed 's/ //g' | base64 -d > /tmp/gcloud_key.json - docker tag codeclimate/codeclimate-csslint $registry_root/$CIRCLE_PROJECT_REPONAME:b$CIRCLE_BUILD_NUM - gcloud auth activate-service-account --key-file /tmp/gcloud_key.json - gcloud docker push $registry_root/$CIRCLE_PROJECT_REPONAME:b$CIRCLE_BUILD_NUM - - # registry.codeclimate.net for CC:E - - docker login --username=$CODECLIMATE_DOCKER_REGISTRY_USERNAME --password=$CODECLIMATE_DOCKER_REGISTRY_PASSWORD --email=$CODECLIMATE_DOCKER_REGISTRY_EMAIL $CODECLIMATE_DOCKER_REGISTRY_HOSTNAME - - docker tag $registry_root/$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: - url: https://cc-slack-proxy.herokuapp.com/circle From d8b3dd17c263e57ff58a9ddd0ea850b29ab003f9 Mon Sep 17 00:00:00 2001 From: Gordon Diggs Date: Mon, 15 Aug 2016 17:14:47 -0400 Subject: [PATCH 35/46] Fix building image - ruby-nokogiri seems to not exist as a package anymore, but a newer version of nokogiri installs a bit more easily, so go with that - Fixed environment name so that we actually pin to this commit --- Dockerfile | 6 +++--- Gemfile.lock | 8 +++++--- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/Dockerfile b/Dockerfile index 92b149d..02b7954 100644 --- a/Dockerfile +++ b/Dockerfile @@ -4,12 +4,12 @@ WORKDIR /usr/src/app COPY Gemfile /usr/src/app/ COPY Gemfile.lock /usr/src/app/ -RUN apk --update add nodejs git ruby ruby-dev ruby-bundler less ruby-nokogiri build-base && \ +RUN apk --update add nodejs git zlib zlib-dev ruby ruby-dev ruby-bundler less build-base && \ bundle install -j 4 && \ - apk del build-base && rm -fr /usr/share/ri + apk del --purge build-base zlib zlib-dev && rm -fr /usr/share/ri ENV CSSLINT_SHA=0e7aeadd02f5babca5152993374043f9f736a116 -RUN npm install -g codeclimate/csslint.git#$CSSLINT_COMMIT +RUN npm install -g codeclimate/csslint.git#$CSSLINT_SHA RUN adduser -u 9000 -D app USER app diff --git a/Gemfile.lock b/Gemfile.lock index cd340ba..99ebd75 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -5,9 +5,11 @@ GEM diff-lcs (1.2.5) json (1.8.3) method_source (0.8.2) - mini_portile (0.6.2) - nokogiri (1.6.6.2) - mini_portile (~> 0.6.0) + mini_portile2 (2.1.0) + nokogiri (1.6.8) + mini_portile2 (~> 2.1.0) + pkg-config (~> 1.1.7) + pkg-config (1.1.7) pry (0.10.1) coderay (~> 1.1.0) method_source (~> 0.8.1) From aa2d1d7c5df3c8e4eb2d461d746591dcf3706eb6 Mon Sep 17 00:00:00 2001 From: Gordon Diggs Date: Mon, 15 Aug 2016 17:16:07 -0400 Subject: [PATCH 36/46] Update CSSLint Updates the underlying parser library --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 02b7954..950153c 100644 --- a/Dockerfile +++ b/Dockerfile @@ -8,7 +8,7 @@ RUN apk --update add nodejs git zlib zlib-dev ruby ruby-dev ruby-bundler less bu bundle install -j 4 && \ apk del --purge build-base zlib zlib-dev && rm -fr /usr/share/ri -ENV CSSLINT_SHA=0e7aeadd02f5babca5152993374043f9f736a116 +ENV CSSLINT_SHA=87aa604a4cbc5125db979576f1b09b35980fcf08 RUN npm install -g codeclimate/csslint.git#$CSSLINT_SHA RUN adduser -u 9000 -D app From a39bb56c0da8be98d811757adbf08b9fd494512e Mon Sep 17 00:00:00 2001 From: Gordon Diggs Date: Mon, 15 Aug 2016 17:28:42 -0400 Subject: [PATCH 37/46] Update circle to use patrick --- circle.yml | 25 +++++++++++++++++++------ 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/circle.yml b/circle.yml index f2c3c0b..43505ee 100644 --- a/circle.yml +++ b/circle.yml @@ -1,8 +1,17 @@ machine: services: - docker - environment: - CLOUDSDK_CORE_DISABLE_PROMPTS: 1 + +dependencies: + override: + - > + docker run + --env CIRCLE_BRANCH + --env CIRCLE_PROJECT_REPONAME + --env CIRCLE_TOKEN + --env GCR_JSON_KEY + --volume /var/run/docker.sock:/var/run/docker.sock + codeclimate/patrick pull || true test: override: @@ -12,10 +21,14 @@ deployment: registry: branch: master commands: - - echo $gcloud_json_key_base64 | sed 's/ //g' | base64 -d > /tmp/gcloud_key.json - - docker tag codeclimate/codeclimate-csslint $registry_root/$CIRCLE_PROJECT_REPONAME:b$CIRCLE_BUILD_NUM - - gcloud auth activate-service-account --key-file /tmp/gcloud_key.json - - gcloud docker push $registry_root/$CIRCLE_PROJECT_REPONAME:b$CIRCLE_BUILD_NUM + - > + docker run + --env CIRCLE_BUILD_NUM + --env CIRCLE_PROJECT_REPONAME + --env GCR_JSON_KEY + --volume /var/run/docker.sock:/var/run/docker.sock + codeclimate/patrick push gcr + notify: webhooks: - url: https://cc-slack-proxy.herokuapp.com/circle From 0daec16b1b7e237da5c51faefdaa40bae7014d74 Mon Sep 17 00:00:00 2001 From: Will Fleming Date: Tue, 16 Aug 2016 17:50:42 -0400 Subject: [PATCH 38/46] Don't crash on parse errors (#34) * Allow passing specific args to rspec in makefile * Don't crash on parse errors Instead of crashing out when encountering a parse error, this will report the issue as a parse error. * cc issues * compact impl * why would you use a tab? --- Makefile | 2 +- lib/cc/engine/csslint.rb | 7 ++++++- lib/cc/engine/csslint/check_details.rb | 1 + spec/cc/engine/csslint_spec.rb | 2 +- 4 files changed, 9 insertions(+), 3 deletions(-) diff --git a/Makefile b/Makefile index f11fb3d..cb4fb3e 100644 --- a/Makefile +++ b/Makefile @@ -4,4 +4,4 @@ image: docker build -t codeclimate/codeclimate-csslint . test: image - docker run --rm codeclimate/codeclimate-csslint rake + docker run --rm codeclimate/codeclimate-csslint rspec $(RSPEC_ARGS) diff --git a/lib/cc/engine/csslint.rb b/lib/cc/engine/csslint.rb index bb25c85..00d15e1 100644 --- a/lib/cc/engine/csslint.rb +++ b/lib/cc/engine/csslint.rb @@ -1,11 +1,14 @@ require "json" require "nokogiri" +require "ostruct" require "shellwords" module CC module Engine MissingAttributesError = Class.new(StandardError) + DEFAULT_IDENTIFIER = OpenStruct.new(value: "parse-error") + class CSSlint autoload :CheckDetails, "cc/engine/csslint/check_details" @@ -30,8 +33,9 @@ def run private + # rubocop:disable Metrics/MethodLength def create_issue(node, path) - check_name = node.attributes.fetch("identifier").value + check_name = node.attributes.fetch("identifier", DEFAULT_IDENTIFIER).value check_details = CheckDetails.fetch(check_name) { @@ -57,6 +61,7 @@ def create_issue(node, path) rescue KeyError => ex raise MissingAttributesError, "#{ex.message} on XML '#{node}' when analyzing file '#{path}'" end + # rubocop:enable Metrics/MethodLength def results @results ||= Nokogiri::XML(csslint_xml) diff --git a/lib/cc/engine/csslint/check_details.rb b/lib/cc/engine/csslint/check_details.rb index 04133d5..d268634 100644 --- a/lib/cc/engine/csslint/check_details.rb +++ b/lib/cc/engine/csslint/check_details.rb @@ -19,6 +19,7 @@ class CheckDetails "import" => { categories: "Bug Risk" }, "known-properties" => { categories: "Bug Risk" }, "overqualified-elements" => { categories: "Bug Risk" }, + "parse-error" => { categories: "Bug Risk" }, "regex-selectors" => { categories: "Bug Risk" }, "shorthand" => { categories: "Bug Risk" }, "star-property-hack" => { categories: "Compatibility" }, diff --git a/spec/cc/engine/csslint_spec.rb b/spec/cc/engine/csslint_spec.rb index 356848c..9c4a40e 100644 --- a/spec/cc/engine/csslint_spec.rb +++ b/spec/cc/engine/csslint_spec.rb @@ -18,7 +18,7 @@ module Engine it 'fails on malformed file' do create_source_file('foo.css', '�6�') - expect{ lint.run }.to raise_error(MissingAttributesError) + expect{ lint.run }.to output(/Unexpected token/).to_stdout end it "doesn't analyze *.scss files" do From cb15a02b6890747fb5ddf1c6f0b9490e514d99f3 Mon Sep 17 00:00:00 2001 From: Alexander Mankuta Date: Tue, 24 Jan 2017 15:08:19 +0200 Subject: [PATCH 39/46] Add custom extensions support --- lib/cc/engine/csslint.rb | 18 ++++++++++++------ spec/cc/engine/csslint_spec.rb | 17 +++++++++++++++++ 2 files changed, 29 insertions(+), 6 deletions(-) diff --git a/lib/cc/engine/csslint.rb b/lib/cc/engine/csslint.rb index 00d15e1..6d94a96 100644 --- a/lib/cc/engine/csslint.rb +++ b/lib/cc/engine/csslint.rb @@ -10,6 +10,8 @@ module Engine DEFAULT_IDENTIFIER = OpenStruct.new(value: "parse-error") class CSSlint + DEFAULT_EXTENSIONS = [".css"].freeze + autoload :CheckDetails, "cc/engine/csslint/check_details" def initialize(directory: , io: , engine_config: ) @@ -33,6 +35,8 @@ def run private + attr_reader :engine_config + # rubocop:disable Metrics/MethodLength def create_issue(node, path) check_name = node.attributes.fetch("identifier", DEFAULT_IDENTIFIER).value @@ -72,14 +76,16 @@ def csslint_xml end def files_to_inspect - include_paths = @engine_config["include_paths"] || ["./"] - include_paths.each_with_object([]) do |path, out| + include_paths = engine_config.fetch("include_paths", ["./"]) + extensions = engine_config.fetch("extensions", DEFAULT_EXTENSIONS) + extensions_glob = extensions.join(",") + include_paths.flat_map do |path| if path.end_with?("/") - out.concat(Dir.glob("#{path}**/*.css")) - elsif path.end_with?(".css") - out << path + Dir.glob("#{File.expand_path path}/**/*{#{extensions_glob}}") + elsif path.end_with?(*extensions) + path end - end + end.compact end end end diff --git a/spec/cc/engine/csslint_spec.rb b/spec/cc/engine/csslint_spec.rb index 9c4a40e..f3362b8 100644 --- a/spec/cc/engine/csslint_spec.rb +++ b/spec/cc/engine/csslint_spec.rb @@ -75,6 +75,23 @@ module Engine expect{ lint.run }.not_to output(/config.yml/).to_stdout end end + + describe "with custom extensions" do + let(:engine_config) do + { + "extensions" => %w(.fancycss) + } + end + + before do + create_source_file("master.fancycss", id_selector_content) + end + + it "takes into account extensions" do + expect{ lint.run }.to \ + output(/Don't use IDs in selectors./).to_stdout + end + end end def create_source_file(path, content) From 6281109e302fe8705c24c1d971a47f09502acc99 Mon Sep 17 00:00:00 2001 From: Gordon Diggs Date: Mon, 30 Jan 2017 16:56:00 -0500 Subject: [PATCH 40/46] Update expectation of where the config is We pass the top-level config for the engine, meaning that the extensions are in a `config` key. --- lib/cc/engine/csslint.rb | 2 +- spec/cc/engine/csslint_spec.rb | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/cc/engine/csslint.rb b/lib/cc/engine/csslint.rb index 6d94a96..fbe3c4d 100644 --- a/lib/cc/engine/csslint.rb +++ b/lib/cc/engine/csslint.rb @@ -77,7 +77,7 @@ def csslint_xml def files_to_inspect include_paths = engine_config.fetch("include_paths", ["./"]) - extensions = engine_config.fetch("extensions", DEFAULT_EXTENSIONS) + extensions = engine_config.fetch("config", {}).fetch("extensions", DEFAULT_EXTENSIONS) extensions_glob = extensions.join(",") include_paths.flat_map do |path| if path.end_with?("/") diff --git a/spec/cc/engine/csslint_spec.rb b/spec/cc/engine/csslint_spec.rb index f3362b8..6b17dfb 100644 --- a/spec/cc/engine/csslint_spec.rb +++ b/spec/cc/engine/csslint_spec.rb @@ -79,7 +79,9 @@ module Engine describe "with custom extensions" do let(:engine_config) do { - "extensions" => %w(.fancycss) + "config" => { + "extensions" => %w(.fancycss) + } } end From 6ce09af64034877c156901b8d2455d3d2068dafa Mon Sep 17 00:00:00 2001 From: Alexander Mankuta Date: Fri, 15 Dec 2017 16:11:50 +0200 Subject: [PATCH 41/46] Rerwrite in JavaScript We-re maintaining our own fork of CSSLink because we need to make some changes to make the output compatible with the Spec. This makes CSSLint updates hard and as a result the engine lags behind the upstream. CSSLint provides APIs that we can use directly to augment output but they're only exposed in JavaScript. This rewrite is targeted towards using the upstream CSSLint and only maintaining the custom parts that in the engine. This should make CSSLint updates trivial as long as the API stays stable. As a bonus it drops Ruby from dependencies making the overal image smaller. --- .codeclimate.yml | 9 +- .dockerignore | 1 + .eslintrc | 32 +++ .gitignore | 1 + .rubocop.yml | 41 --- .ruby-version | 1 - Dockerfile | 17 +- Gemfile | 10 - Gemfile.lock | 44 --- Makefile | 2 +- Rakefile | 5 - bin/csslint | 18 +- bin/csslint.rb | 14 + lib/cc/engine/csslint.rb | 92 ------- lib/cc/engine/csslint/check_details.rb | 54 ---- lib/check-details.js | 44 +++ lib/codeclimate-formatter.js | 78 ++++++ lib/csslint.js | 120 ++++++++ package.json | 25 ++ spec/cc/engine/csslint/check_details_spec.rb | 21 -- spec/cc/engine/csslint_spec.rb | 106 -------- spec/spec_helper.rb | 2 - test/check-details-test.js | 18 ++ test/codeclimate-formatter_test.js | 54 ++++ test/csslint-test.js | 127 +++++++++ yarn.lock | 272 +++++++++++++++++++ 26 files changed, 806 insertions(+), 402 deletions(-) create mode 100644 .eslintrc create mode 100644 .gitignore delete mode 100644 .rubocop.yml delete mode 100644 .ruby-version delete mode 100644 Gemfile delete mode 100644 Gemfile.lock delete mode 100644 Rakefile create mode 100755 bin/csslint.rb delete mode 100644 lib/cc/engine/csslint.rb delete mode 100644 lib/cc/engine/csslint/check_details.rb create mode 100644 lib/check-details.js create mode 100644 lib/codeclimate-formatter.js create mode 100644 lib/csslint.js create mode 100644 package.json delete mode 100644 spec/cc/engine/csslint/check_details_spec.rb delete mode 100644 spec/cc/engine/csslint_spec.rb delete mode 100644 spec/spec_helper.rb create mode 100644 test/check-details-test.js create mode 100644 test/codeclimate-formatter_test.js create mode 100644 test/csslint-test.js create mode 100644 yarn.lock diff --git a/.codeclimate.yml b/.codeclimate.yml index 1b449f2..42e43fe 100644 --- a/.codeclimate.yml +++ b/.codeclimate.yml @@ -1,8 +1,7 @@ engines: - rubocop: + eslint: enabled: true -ratings: - paths: - - "**.rb" + channel: eslint-4 exclude_paths: - - spec/**/* + - test/**/* + - node_modules/**/* diff --git a/.dockerignore b/.dockerignore index cf6ce25..01d2a07 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,2 +1,3 @@ .git Makefile +node_modules diff --git a/.eslintrc b/.eslintrc new file mode 100644 index 0000000..63664eb --- /dev/null +++ b/.eslintrc @@ -0,0 +1,32 @@ +{ + "env": { + "node": true, + "mocha": true, + "es6": true + }, + "rules": { + "block-spacing": 2, + "brace-style": [2, "1tbs", { "allowSingleLine": true }], + "comma-dangle": [2, "never"], + "comma-style": [2, "first", { exceptions: {ArrayExpression: true, ObjectExpression: true} }], + "complexity": [2, 6], + "curly": 2, + "eqeqeq": [2, "allow-null"], + "max-statements": [2, 30], + "no-shadow-restricted-names": 2, + "no-undef": 2, + "no-use-before-define": 2, + "radix": 2, + "semi": 2, + "space-infix-ops": 2, + "strict": 0 + }, + "globals": { + "AnalysisView": true, + "PollingView": true, + "Prism": true, + "Spinner": true, + "Timer": true, + "moment": true + } +} diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c2658d7 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +node_modules/ diff --git a/.rubocop.yml b/.rubocop.yml deleted file mode 100644 index 6583489..0000000 --- a/.rubocop.yml +++ /dev/null @@ -1,41 +0,0 @@ -Style/StringLiterals: - Enabled: false - -Style/Documentation: - Enabled: false - -Metrics/LineLength: - Enabled: false - -Style/TrailingComma: - Enabled: false - -Style/FileName: - Exclude: - - 'bin/**/*' - -Style/ClassAndModuleChildren: - Exclude: - - 'spec/**/*' - -Metrics/ModuleLength: - Exclude: - - 'spec/**/*' - -Style/GuardClause: - Enabled: false - -Style/IfUnlessModifier: - Enabled: false - -Style/DotPosition: - Enabled: false - -Style/SignalException: - Enabled: false - -Metrics/AbcSize: - Enabled: false - -Rails/TimeZone: - Enabled: false diff --git a/.ruby-version b/.ruby-version deleted file mode 100644 index 5859406..0000000 --- a/.ruby-version +++ /dev/null @@ -1 +0,0 @@ -2.2.3 diff --git a/Dockerfile b/Dockerfile index 950153c..b3c90a9 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,17 +1,16 @@ -FROM codeclimate/alpine-ruby:b38 +FROM node:alpine + +RUN adduser -u 9000 -D app WORKDIR /usr/src/app -COPY Gemfile /usr/src/app/ -COPY Gemfile.lock /usr/src/app/ -RUN apk --update add nodejs git zlib zlib-dev ruby ruby-dev ruby-bundler less build-base && \ - bundle install -j 4 && \ - apk del --purge build-base zlib zlib-dev && rm -fr /usr/share/ri +COPY package.json yarn.lock ./ -ENV CSSLINT_SHA=87aa604a4cbc5125db979576f1b09b35980fcf08 -RUN npm install -g codeclimate/csslint.git#$CSSLINT_SHA +RUN yarn install && \ + chown -R app:app ./ + +COPY . ./ -RUN adduser -u 9000 -D app USER app COPY . /usr/src/app diff --git a/Gemfile b/Gemfile deleted file mode 100644 index 76ec3ca..0000000 --- a/Gemfile +++ /dev/null @@ -1,10 +0,0 @@ -source "https://rubygems.org" - -gem 'json' -gem 'nokogiri' -gem "pry" - -group :test do - gem "rake" - gem "rspec" -end diff --git a/Gemfile.lock b/Gemfile.lock deleted file mode 100644 index 99ebd75..0000000 --- a/Gemfile.lock +++ /dev/null @@ -1,44 +0,0 @@ -GEM - remote: https://rubygems.org/ - specs: - coderay (1.1.0) - diff-lcs (1.2.5) - json (1.8.3) - method_source (0.8.2) - mini_portile2 (2.1.0) - nokogiri (1.6.8) - mini_portile2 (~> 2.1.0) - pkg-config (~> 1.1.7) - pkg-config (1.1.7) - pry (0.10.1) - coderay (~> 1.1.0) - method_source (~> 0.8.1) - slop (~> 3.4) - rake (10.4.2) - rspec (3.2.0) - rspec-core (~> 3.2.0) - rspec-expectations (~> 3.2.0) - rspec-mocks (~> 3.2.0) - rspec-core (3.2.3) - rspec-support (~> 3.2.0) - rspec-expectations (3.2.1) - diff-lcs (>= 1.2.0, < 2.0) - rspec-support (~> 3.2.0) - rspec-mocks (3.2.1) - diff-lcs (>= 1.2.0, < 2.0) - rspec-support (~> 3.2.0) - rspec-support (3.2.2) - slop (3.6.0) - -PLATFORMS - ruby - -DEPENDENCIES - json - nokogiri - pry - rake - rspec - -BUNDLED WITH - 1.10.6 diff --git a/Makefile b/Makefile index cb4fb3e..f83f495 100644 --- a/Makefile +++ b/Makefile @@ -4,4 +4,4 @@ image: docker build -t codeclimate/codeclimate-csslint . test: image - docker run --rm codeclimate/codeclimate-csslint rspec $(RSPEC_ARGS) + docker run --rm codeclimate/codeclimate-csslint npm run test diff --git a/Rakefile b/Rakefile deleted file mode 100644 index 8281788..0000000 --- a/Rakefile +++ /dev/null @@ -1,5 +0,0 @@ -require "rspec/core/rake_task" - -RSpec::Core::RakeTask.new(:spec) - -task default: :spec diff --git a/bin/csslint b/bin/csslint index 8b2417d..d9c7377 100755 --- a/bin/csslint +++ b/bin/csslint @@ -1,14 +1,10 @@ -#!/usr/bin/env ruby -$LOAD_PATH.unshift(File.expand_path(File.join(File.dirname(__FILE__), "../lib"))) +#!/usr/local/bin/node --expose-gc -require 'cc/engine/csslint' +const fs = require("fs"); +const Engine = require("../lib/csslint"); -if File.exists?("/config.json") - engine_config = JSON.parse(File.read("/config.json")) -else - engine_config = {} -end +const CONFIG_PATH = "/config.json"; +let config = JSON.parse(fs.readFileSync(CONFIG_PATH)); -CC::Engine::CSSlint.new( - directory: "/code", engine_config: engine_config, io: STDOUT -).run +const CODE_DIR = "/code"; +new Engine(CODE_DIR, console, config).run(); diff --git a/bin/csslint.rb b/bin/csslint.rb new file mode 100755 index 0000000..8b2417d --- /dev/null +++ b/bin/csslint.rb @@ -0,0 +1,14 @@ +#!/usr/bin/env ruby +$LOAD_PATH.unshift(File.expand_path(File.join(File.dirname(__FILE__), "../lib"))) + +require 'cc/engine/csslint' + +if File.exists?("/config.json") + engine_config = JSON.parse(File.read("/config.json")) +else + engine_config = {} +end + +CC::Engine::CSSlint.new( + directory: "/code", engine_config: engine_config, io: STDOUT +).run diff --git a/lib/cc/engine/csslint.rb b/lib/cc/engine/csslint.rb deleted file mode 100644 index fbe3c4d..0000000 --- a/lib/cc/engine/csslint.rb +++ /dev/null @@ -1,92 +0,0 @@ -require "json" -require "nokogiri" -require "ostruct" -require "shellwords" - -module CC - module Engine - MissingAttributesError = Class.new(StandardError) - - DEFAULT_IDENTIFIER = OpenStruct.new(value: "parse-error") - - class CSSlint - DEFAULT_EXTENSIONS = [".css"].freeze - - autoload :CheckDetails, "cc/engine/csslint/check_details" - - def initialize(directory: , io: , engine_config: ) - @directory = directory - @engine_config = engine_config - @io = io - end - - def run - Dir.chdir(@directory) do - results.xpath('//file').each do |file| - path = file['name'].sub(/\A#{@directory}\//, '') - file.children.each do |node| - next unless node.name == "error" - issue = create_issue(node, path) - puts("#{issue.to_json}\0") - end - end - end - end - - private - - attr_reader :engine_config - - # rubocop:disable Metrics/MethodLength - def create_issue(node, path) - check_name = node.attributes.fetch("identifier", DEFAULT_IDENTIFIER).value - check_details = CheckDetails.fetch(check_name) - - { - type: "issue", - check_name: check_name, - description: node.attributes.fetch("message").value, - categories: check_details.categories, - remediation_points: check_details.remediation_points, - location: { - path: path, - positions: { - begin: { - line: node.attributes.fetch("line").value.to_i, - column: node.attributes.fetch("column").value.to_i - }, - end: { - line: node.attributes.fetch("line").value.to_i, - column: node.attributes.fetch("column").value.to_i - } - } - } - } - rescue KeyError => ex - raise MissingAttributesError, "#{ex.message} on XML '#{node}' when analyzing file '#{path}'" - end - # rubocop:enable Metrics/MethodLength - - def results - @results ||= Nokogiri::XML(csslint_xml) - end - - def csslint_xml - `csslint --format=checkstyle-xml #{files_to_inspect.shelljoin}` - end - - def files_to_inspect - include_paths = engine_config.fetch("include_paths", ["./"]) - extensions = engine_config.fetch("config", {}).fetch("extensions", DEFAULT_EXTENSIONS) - extensions_glob = extensions.join(",") - include_paths.flat_map do |path| - if path.end_with?("/") - Dir.glob("#{File.expand_path path}/**/*{#{extensions_glob}}") - elsif path.end_with?(*extensions) - path - end - end.compact - end - end - end -end diff --git a/lib/cc/engine/csslint/check_details.rb b/lib/cc/engine/csslint/check_details.rb deleted file mode 100644 index d268634..0000000 --- a/lib/cc/engine/csslint/check_details.rb +++ /dev/null @@ -1,54 +0,0 @@ -module CC - module Engine - class CSSlint - class CheckDetails - ALL_RULES = { - # https://github.com/CSSLint/csslint/wiki/Rules - "adjoining-classes" => { categories: "Compatibility" }, - "box-model" => { categories: "Bug Risk" }, - "box-sizing" => { categories: "Compatibility" }, - "bulletproof-font-face" => { categories: "Compatibility" }, - "compatible-vendor-prefixes" => { categories: "Compatibility" }, - "display-property-grouping" => { categories: "Bug Risk" }, - "duplicate-background-images" => { categories: "Bug Risk" }, - "duplicate-properties" => { categories: "Bug Risk" }, - "empty-rules" => { categories: "Bug Risk" }, - "fallback-colors" => { categories: "Compatibility" }, - "font-faces" => { categories: "Bug Risk" }, - "gradients" => { categories: "Compatibility" }, - "import" => { categories: "Bug Risk" }, - "known-properties" => { categories: "Bug Risk" }, - "overqualified-elements" => { categories: "Bug Risk" }, - "parse-error" => { categories: "Bug Risk" }, - "regex-selectors" => { categories: "Bug Risk" }, - "shorthand" => { categories: "Bug Risk" }, - "star-property-hack" => { categories: "Compatibility" }, - "text-indent" => { categories: "Compatibility" }, - "underscore-property-hack" => { categories: "Compatibility" }, - "unique-headings" => { categories: "Duplication" }, - "universal-selector" => { categories: "Bug Risk" }, - "unqualified-attributes" => { categories: "Bug Risk" }, - "vendor-prefix" => { categories: "Compatibility" }, - "zero-units" => { categories: "Bug Risk" }, - }.freeze - - DEFAULT_CATEGORY = "Style".freeze - DEFAULT_REMEDIATION_POINTS = 50_000.freeze - - attr_reader :categories, :remediation_points - - def self.fetch(check_name) - new(ALL_RULES.fetch(check_name, {})) - end - - def initialize( - categories: DEFAULT_CATEGORY, - remediation_points: DEFAULT_REMEDIATION_POINTS - ) - @categories = Array(categories) - @remediation_points = remediation_points - end - end - end - end -end diff --git a/lib/check-details.js b/lib/check-details.js new file mode 100644 index 0000000..13f9e47 --- /dev/null +++ b/lib/check-details.js @@ -0,0 +1,44 @@ +// https://github.com/CSSLint/csslint/wiki/Rules +const ALL_RULES = { + "adjoining-classes": "Compatibility", + "box-model": "Bug Risk", + "box-sizing": "Compatibility", + "bulletproof-font-face": "Compatibility", + "compatible-vendor-prefixes": "Compatibility", + "display-property-grouping": "Bug Risk", + "duplicate-background-images": "Performance", + "duplicate-properties": "Bug Risk", + "empty-rules": "Bug Risk", + "fallback-colors": "Compatibility", + "floats": "Clarity", + "font-faces": "Performance", + "font-sizes": "Clarity", + "gradients": "Compatibility", + "ids": "Complexity", + "import": "Performance", + "important": "Complexity", + "known-properties": "Bug Risk", + "overqualified-elements": "Performance", + "parse-error": "Bug Risk", + "regex-selectors": "Performance", + "shorthand": "Performance", + "star-property-hack": "Compatibility", + "text-indent": "Compatibility", + "underscore-property-hack": "Compatibility", + "unique-headings": "Duplication", + "universal-selector": "Performance", + "unqualified-attributes": "Performance", + "vendor-prefix": "Compatibility", + "zero-units": "Performance" +}; + +const DEFAULT_CATEGORY = "Style"; +const DEFAULT_REMEDIATION_POINTS = 50000; + +module.exports = function(check_name) { + let category = ALL_RULES[check_name] || DEFAULT_CATEGORY; + return { + categories: [category], + remediation_points: DEFAULT_REMEDIATION_POINTS + }; +}; diff --git a/lib/codeclimate-formatter.js b/lib/codeclimate-formatter.js new file mode 100644 index 0000000..ed9f03b --- /dev/null +++ b/lib/codeclimate-formatter.js @@ -0,0 +1,78 @@ +"use strict"; + +const checkDetails = require('./check-details'); + +const DEFAULT_IDENTIFIER = "parse-error"; +const forEach = require("csslint/dist/csslint-node").CSSLint.Util.forEach; + +function ruleIdentifier(rule) { + if (!rule || !("id" in rule)) { + return "generic"; + } + return rule.id; +}; + +function reportJSON(filename, report) { + let check_name = report.rule ? ruleIdentifier(report.rule) : DEFAULT_IDENTIFIER; + let details = checkDetails(check_name); + + return JSON.stringify({ + type: "issue", + check_name: check_name, + description: report.message, + categories: details.categories, + remediation_points: details.remediation_points, + location: { + path: filename, + positions: { + begin: { + line: report.line || 1, + column: report.col || 1 + }, + end: { + line: report.line || 1, + column: report.col || 1 + } + } + } + }) + "\x00"; +} + +module.exports = { + // format information + id: "codeclimate", + name: "Code Climate format", + + startFormat: function() { + return ""; + }, + endFormat: function() { + return ""; + }, + + readError: function(filename, message) { + let report = { + type: "error", + line: 1, + col: 1, + message : message + }; + return reportJSON(filename, report); + }, + + formatResults: function(results, filename/*, options*/) { + let reports = results.messages; + let output = []; + + if (reports.length > 0) { + forEach(reports, function (report) { + // ignore rollups for now + if (!report.rollup) { + output.push(reportJSON(filename, report)); + } + }); + } + + return output.join(""); + } +}; diff --git a/lib/csslint.js b/lib/csslint.js new file mode 100644 index 0000000..bf1dffa --- /dev/null +++ b/lib/csslint.js @@ -0,0 +1,120 @@ +"use strict"; + +const fs = require("fs"); +const path = require("path"); +const glob = require("glob"); +const CSSLint = require("csslint/dist/csslint-node").CSSLint; +const CodeClimateFormatter = require("./codeclimate-formatter"); + +const DEFAULT_EXTENSIONS = [".css"]; + +CSSLint.addFormatter(CodeClimateFormatter); + +function readFile(filename) { + try { + return fs.readFileSync(filename, "utf-8"); + } catch (ex) { + return ""; + } +} + + +class Analyzer { + constructor(directory, console, config) { + this.directory = directory; + this.console = console; + this.config = config; + } + + run() { + let files = this.expandPaths(this.config.include_paths || ["./"]); + + this.processFiles(files); + } + + + // private ================================================================= + + + print(message) { + this.console.log(message); + } + + processFile(relativeFilePath) { + let input = readFile(path.join(this.directory, relativeFilePath)); + let formatter = CSSLint.getFormatter("codeclimate"); + + if (!input) { + this.print(formatter.readError(relativeFilePath, "Could not read file data. Is the file empty?")); + } else { + let result = CSSLint.verify(input); + let messages = result.messages || []; + let output = formatter.formatResults(result, relativeFilePath); + if (output) { + this.print(output); + } + } + } + + + processFiles(files) { + for (let file of files) { + this.processFile(file); + } + } + + + expandPaths(paths) { + let files = []; + + for (let path of paths) { + let new_files = this.getFiles(path, this.directory); + files = files.concat(new_files); + } + + return files; + } + + getFiles(pathname) { + var files = []; + let full_pathname = path.normalize(path.join(this.directory, pathname)); + let stat; + let base_name = path.basename(pathname); + + try { + stat = fs.statSync(full_pathname); + } catch (ex) { + return []; + } + + if (stat.isFile() && this.extensionsRegExp.test(full_pathname)) { + return [pathname]; + } else if (stat.isDirectory()) { + for (let file of fs.readdirSync(full_pathname)) { + let new_path = path.join(full_pathname, file); + files = files.concat(this.getFiles(path.relative(this.directory, new_path))); + }; + } + + return files; + } + + get extensionsRegExp() { + return RegExp( + (this.config.extensions || DEFAULT_EXTENSIONS). + map(e => e.replace('.', '\\.')). + join("|") + + "$" + ); + } +} + +module.exports = class { + constructor(directory, console, config) { + this.analyzer = new Analyzer(directory, console, config); + } + + run() { + this.analyzer.run(); + } +}; diff --git a/package.json b/package.json new file mode 100644 index 0000000..2c01c33 --- /dev/null +++ b/package.json @@ -0,0 +1,25 @@ +{ + "name": "codeclimate-csslint", + "version": "1.0.0", + "description": "Code Climate CSSLint Engine", + "repository": { + "type": "git", + "url": "https://github.com/codeclimate/codeclimate-csslint.git" + }, + "author": "Code Climate", + "license": "MIT", + "dependencies": { + "csslint": "^1.0.5", + "glob": "^7.1.2" + }, + "devDependencies": { + "chai": "^4.1.2", + "mkdirp": "^0.5.1", + "mocha": "^4.0.1", + "sinon": "^4.1.3", + "temp": "^0.8.3" + }, + "scripts": { + "test": "mocha -gc test" + } +} diff --git a/spec/cc/engine/csslint/check_details_spec.rb b/spec/cc/engine/csslint/check_details_spec.rb deleted file mode 100644 index 134dbb6..0000000 --- a/spec/cc/engine/csslint/check_details_spec.rb +++ /dev/null @@ -1,21 +0,0 @@ -require "spec_helper" - -class CC::Engine::CSSlint - describe CheckDetails do - describe ".fetch" do - it "returns details for customized checks" do - details = CheckDetails.fetch("import") - - expect(details.categories).to eq ["Bug Risk"] - expect(details.remediation_points).to eq 50_000 - end - - it "returns defaults for unknown checks" do - details = CheckDetails.fetch("made-up") - - expect(details.categories).to eq ["Style"] - expect(details.remediation_points).to eq 50_000 - end - end - end -end diff --git a/spec/cc/engine/csslint_spec.rb b/spec/cc/engine/csslint_spec.rb deleted file mode 100644 index 6b17dfb..0000000 --- a/spec/cc/engine/csslint_spec.rb +++ /dev/null @@ -1,106 +0,0 @@ -require "spec_helper" - -module CC - module Engine - describe CSSlint do - let(:code) { Dir.mktmpdir } - let(:engine_config) { {} } - let(:lint) do - CSSlint.new(directory: code, io: nil, engine_config: engine_config) - end - let(:id_selector_content) { '#id { color: red; }' } - - describe '#run' do - it 'analyzes *.css files' do - create_source_file('foo.css', id_selector_content) - expect{ lint.run }.to output(/Don't use IDs in selectors./).to_stdout - end - - it 'fails on malformed file' do - create_source_file('foo.css', '�6�') - expect{ lint.run }.to output(/Unexpected token/).to_stdout - end - - it "doesn't analyze *.scss files" do - create_source_file('foo.scss', id_selector_content) - expect{ lint.run }.to_not output.to_stdout - end - - it "only reports issues in the file where they're present" do - create_source_file('bad.css', id_selector_content) - create_source_file('good.css', '.foo { margin: 0 }') - expect{ lint.run }.not_to output(/good\.css/).to_stdout - end - - describe "with include_paths" do - let(:engine_config) { - {"include_paths" => %w(included.css included_dir/ config.yml)} - } - - before do - create_source_file("included.css", id_selector_content) - create_source_file( - "included_dir/file.css", "p { color: blue !important; }" - ) - create_source_file( - "included_dir/sub/sub/subdir/file.css", "img { }" - ) - create_source_file("config.yml", "foo:\n bar: \"baz\"") - create_source_file("not_included.css", "a { outline: none; }") - end - - it "includes all mentioned files" do - expect{ lint.run }.to \ - output(/Don't use IDs in selectors./).to_stdout - end - - it "expands directories" do - expect{ lint.run }.to output(/Use of !important/).to_stdout - expect{ lint.run }.to output(/Rule is empty/).to_stdout - end - - it "excludes any unmentioned files" do - expect{ lint.run }.not_to \ - output(/Outlines should only be modified using :focus/).to_stdout - end - - it "shouldn't call a top-level Dir.glob ever" do - allow(Dir).to receive(:glob).and_call_original - expect(Dir).not_to receive(:glob).with("**/*.css") - expect{ lint.run }.to \ - output(/Don't use IDs in selectors./).to_stdout - end - - it "only includes CSS files, even when a non-CSS file is directly included" do - expect{ lint.run }.not_to output(/config.yml/).to_stdout - end - end - - describe "with custom extensions" do - let(:engine_config) do - { - "config" => { - "extensions" => %w(.fancycss) - } - } - end - - before do - create_source_file("master.fancycss", id_selector_content) - end - - it "takes into account extensions" do - expect{ lint.run }.to \ - output(/Don't use IDs in selectors./).to_stdout - end - end - end - - def create_source_file(path, content) - abs_path = File.join(code, path) - FileUtils.mkdir_p(File.dirname(abs_path)) - File.write(abs_path, content) - end - end - end -end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb deleted file mode 100644 index 2be99e0..0000000 --- a/spec/spec_helper.rb +++ /dev/null @@ -1,2 +0,0 @@ -require "cc/engine/csslint" -require "tmpdir" diff --git a/test/check-details-test.js b/test/check-details-test.js new file mode 100644 index 0000000..bb8646b --- /dev/null +++ b/test/check-details-test.js @@ -0,0 +1,18 @@ +const checkDetails = require("../lib/check-details"); +const expect = require("chai").expect; + +describe("Check Details", function() { + it("returns details for customized checks", function() { + let details = checkDetails("import"); + + expect(details.categories).to.deep.equal(["Performance"]); + expect(details.remediation_points).to.eq(50000); + }); + + it("returns defauls for unknown checks", function() { + let details = checkDetails("made-up"); + + expect(details.categories).to.deep.equal(["Style"]); + expect(details.remediation_points).to.eq(50000); + }); +}); diff --git a/test/codeclimate-formatter_test.js b/test/codeclimate-formatter_test.js new file mode 100644 index 0000000..ad7eb10 --- /dev/null +++ b/test/codeclimate-formatter_test.js @@ -0,0 +1,54 @@ +const Formatter = require("../lib/codeclimate-formatter"); +const expect = require("chai").expect; +const CSSLint = require("csslint/dist/csslint-node").CSSLint; + +describe("Code Climate Formatter", function() { + describe(".startFormat", function() { + it("returns a blank string", function() { + expect(Formatter.startFormat()).to.eq(""); + }); + }); + + describe(".endFormat", function() { + it("returns a blank string", function() { + expect(Formatter.endFormat()).to.eq(""); + }); + }); + + describe(".readError", function() { + it("properly serializes a read error", function() { + expect(Formatter.readError("foo.css", "Can not read the file")).to.eq( + '{"type":"issue","check_name":"parse-error","description":"Can not read the file","categories":["Bug Risk"],"remediation_points":50000,"location":{"path":"foo.css","positions":{"begin":{"line":1,"column":1},"end":{"line":1,"column":1}}}}\x00' + ); + }); + }); + + describe(".formatResults", function() { + it("properly serializes reports", function() { + let reports = [ + { + type: "warning", + line: 1, + col: 1, + message: "Don't use adjoining classes.", + evidence: ".im-bad {", + rule: CSSLint.getRules().find( rule => rule.id === "adjoining-classes") + }, + { + type: "warning", + line: 10, + col: 1, + message: "Disallow empty rules", + evidence: ".empty {}", + rule: CSSLint.getRules().find( rule => rule.id === "empty-rules") + } + ]; + + expect(Formatter.formatResults({messages: reports}, "foo.css")).to.eq( + '{"type":"issue","check_name":"adjoining-classes","description":"Don\'t use adjoining classes.","categories":["Compatibility"],"remediation_points":50000,"location":{"path":"foo.css","positions":{"begin":{"line":1,"column":1},"end":{"line":1,"column":1}}}}\x00' + + '{"type":"issue","check_name":"empty-rules","description":"Disallow empty rules","categories":["Bug Risk"],"remediation_points":50000,"location":{"path":"foo.css","positions":{"begin":{"line":10,"column":1},"end":{"line":10,"column":1}}}}\x00' + ); + }); + }); + +}); diff --git a/test/csslint-test.js b/test/csslint-test.js new file mode 100644 index 0000000..8892f37 --- /dev/null +++ b/test/csslint-test.js @@ -0,0 +1,127 @@ +const Engine = require("../lib/csslint"); +const expect = require("chai").expect; +const CSSLint = require("csslint/dist/csslint-node").CSSLint; +const temp = require('temp').track(); +const fs = require("fs"); +const path = require("path"); +const mkdirp = require('mkdirp').sync; + +class FakeConsole { + constructor() { + this.logs = []; + this.warns = []; + } + + get output() { + return this.logs.join("\n"); + } + + + log(str) { + this.logs.push(str); + } + + warn(str) { + console.warn(str); + this.warns.push(str); + } +} + + +function createSourceFile(root, filename, content) { + let dirname = path.dirname(path.join(root, filename)); + if (!fs.existsSync(dirname)) { + mkdirp(dirname); + } + fs.writeFileSync(path.join(root, filename), content); +} + +describe("CSSLint Engine", function() { + beforeEach(function(){ + this.id_selector_content = "#id { color: red; }"; + this.code_dir = temp.mkdirSync("code"); + this.console = new FakeConsole(); + this.lint = new Engine(this.code_dir, this.console, {}); + }); + + it('analyzes *.css files', function() { + createSourceFile(this.code_dir, 'foo.css', this.id_selector_content); + + this.lint.run(); + expect(this.console.output).to.include("Don't use IDs in selectors."); + }); + + it('fails on malformed file', function() { + createSourceFile(this.code_dir, 'foo.css', '�6�'); + + this.lint.run(); + expect(this.console.output).to.include('Unexpected token'); + }); + + it("doesn't analyze *.scss files", function() { + createSourceFile(this.code_dir, 'foo.scss', this.id_selector_content); + + this.lint.run(); + expect(this.console.output).to.eq(''); + }); + + it("only reports issues in the file where they're present", function() { + createSourceFile(this.code_dir, 'bad.css', this.id_selector_content); + createSourceFile(this.code_dir, 'good.css', '.foo { margin: 0 }'); + + this.lint.run(); + expect(this.console.output).to.not.include('good.css'); + }); + + context("with include_paths", function(){ + beforeEach(function() { + let engine_config = { + include_paths: ["included.css", "included_dir/", "config.yml"] + }; + this.lint = new Engine(this.code_dir, this.console, engine_config); + + createSourceFile(this.code_dir, "included.css", this.id_selector_content); + createSourceFile(this.code_dir, "included_dir/file.css", "p { color: blue !important; }"); + createSourceFile(this.code_dir, "included_dir/sub/sub/subdir/file.css", "img { }"); + createSourceFile(this.code_dir, "config.yml", "foo:\n bar: \"baz\""); + createSourceFile(this.code_dir, "not_included.css", "a { outline: none; }"); + }); + + it("includes all mentioned files", function() { + this.lint.run(); + expect(this.console.output).to.include("Don't use IDs in selectors."); + }); + + it("expands directories", function() { + this.lint.run(); + expect(this.console.output).to.include('Use of !important'); + expect(this.console.output).to.include('Rule is empty'); + }); + + it("excludes any unmentioned files", function() { + this.lint.run(); + expect(this.console.output).to.not.include('Outlines should only be modified using :focus'); + }); + + it("only includes CSS files, even when a non-CSS file is directly included", function() { + this.lint.run(); + expect(this.console.output).to.not.include('config.yml'); + }); + }); + + context("with custom extensions", function(){ + beforeEach(function() { + let engine_config = { + extensions: [".fancycss"] + }; + this.lint = new Engine(this.code_dir, this.console, engine_config); + + createSourceFile(this.code_dir, "master.fancycss", this.id_selector_content); + }); + + it("takes into account extensions", function() { + this.lint.run(); + expect(this.console.output).to.include("Don't use IDs in selectors."); + }); + }); +}); diff --git a/yarn.lock b/yarn.lock new file mode 100644 index 0000000..99a6eac --- /dev/null +++ b/yarn.lock @@ -0,0 +1,272 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +assertion-error@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/assertion-error/-/assertion-error-1.0.2.tgz#13ca515d86206da0bac66e834dd397d87581094c" + +balanced-match@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767" + +brace-expansion@^1.1.7: + version "1.1.8" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.8.tgz#c07b211c7c952ec1f8efd51a77ef0d1d3990a292" + dependencies: + balanced-match "^1.0.0" + concat-map "0.0.1" + +browser-stdout@1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/browser-stdout/-/browser-stdout-1.3.0.tgz#f351d32969d32fa5d7a5567154263d928ae3bd1f" + +chai@^4.1.2: + version "4.1.2" + resolved "https://registry.yarnpkg.com/chai/-/chai-4.1.2.tgz#0f64584ba642f0f2ace2806279f4f06ca23ad73c" + dependencies: + assertion-error "^1.0.1" + check-error "^1.0.1" + deep-eql "^3.0.0" + get-func-name "^2.0.0" + pathval "^1.0.0" + type-detect "^4.0.0" + +check-error@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/check-error/-/check-error-1.0.2.tgz#574d312edd88bb5dd8912e9286dd6c0aed4aac82" + +clone@~2.1.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/clone/-/clone-2.1.1.tgz#d217d1e961118e3ac9a4b8bba3285553bf647cdb" + +commander@2.11.0: + version "2.11.0" + resolved "https://registry.yarnpkg.com/commander/-/commander-2.11.0.tgz#157152fd1e7a6c8d98a5b715cf376df928004563" + +concat-map@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" + +csslint@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/csslint/-/csslint-1.0.5.tgz#19cc3eda322160fd3f7232af1cb2a360e898a2e9" + dependencies: + clone "~2.1.0" + parserlib "~1.1.1" + +debug@3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/debug/-/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261" + dependencies: + ms "2.0.0" + +deep-eql@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/deep-eql/-/deep-eql-3.0.1.tgz#dfc9404400ad1c8fe023e7da1df1c147c4b444df" + dependencies: + type-detect "^4.0.0" + +diff@3.3.1: + version "3.3.1" + resolved "https://registry.yarnpkg.com/diff/-/diff-3.3.1.tgz#aa8567a6eed03c531fc89d3f711cd0e5259dec75" + +diff@^3.1.0: + version "3.4.0" + resolved "https://registry.yarnpkg.com/diff/-/diff-3.4.0.tgz#b1d85507daf3964828de54b37d0d73ba67dda56c" + +escape-string-regexp@1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" + +formatio@1.2.0, formatio@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/formatio/-/formatio-1.2.0.tgz#f3b2167d9068c4698a8d51f4f760a39a54d818eb" + dependencies: + samsam "1.x" + +fs.realpath@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" + +get-func-name@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/get-func-name/-/get-func-name-2.0.0.tgz#ead774abee72e20409433a066366023dd6887a41" + +glob@7.1.2, glob@^7.1.2: + version "7.1.2" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.2.tgz#c19c9df9a028702d678612384a6552404c636d15" + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^3.0.4" + once "^1.3.0" + path-is-absolute "^1.0.0" + +growl@1.10.3: + version "1.10.3" + resolved "https://registry.yarnpkg.com/growl/-/growl-1.10.3.tgz#1926ba90cf3edfe2adb4927f5880bc22c66c790f" + +has-flag@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-2.0.0.tgz#e8207af1cc7b30d446cc70b734b5e8be18f88d51" + +he@1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/he/-/he-1.1.1.tgz#93410fd21b009735151f8868c2f271f3427e23fd" + +inflight@^1.0.4: + version "1.0.6" + resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" + dependencies: + once "^1.3.0" + wrappy "1" + +inherits@2: + version "2.0.3" + resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" + +isarray@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/isarray/-/isarray-0.0.1.tgz#8a18acfca9a8f4177e09abfc6038939b05d1eedf" + +just-extend@^1.1.26: + version "1.1.27" + resolved "https://registry.yarnpkg.com/just-extend/-/just-extend-1.1.27.tgz#ec6e79410ff914e472652abfa0e603c03d60e905" + +lodash.get@^4.4.2: + version "4.4.2" + resolved "https://registry.yarnpkg.com/lodash.get/-/lodash.get-4.4.2.tgz#2d177f652fa31e939b4438d5341499dfa3825e99" + +lolex@^1.6.0: + version "1.6.0" + resolved "https://registry.yarnpkg.com/lolex/-/lolex-1.6.0.tgz#3a9a0283452a47d7439e72731b9e07d7386e49f6" + +lolex@^2.2.0: + version "2.3.1" + resolved "https://registry.yarnpkg.com/lolex/-/lolex-2.3.1.tgz#3d2319894471ea0950ef64692ead2a5318cff362" + +minimatch@^3.0.4: + version "3.0.4" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" + dependencies: + brace-expansion "^1.1.7" + +minimist@0.0.8: + version "0.0.8" + resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d" + +mkdirp@0.5.1, mkdirp@^0.5.1: + version "0.5.1" + resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903" + dependencies: + minimist "0.0.8" + +mocha@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/mocha/-/mocha-4.0.1.tgz#0aee5a95cf69a4618820f5e51fa31717117daf1b" + dependencies: + browser-stdout "1.3.0" + commander "2.11.0" + debug "3.1.0" + diff "3.3.1" + escape-string-regexp "1.0.5" + glob "7.1.2" + growl "1.10.3" + he "1.1.1" + mkdirp "0.5.1" + supports-color "4.4.0" + +ms@2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" + +nise@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/nise/-/nise-1.2.0.tgz#079d6cadbbcb12ba30e38f1c999f36ad4d6baa53" + dependencies: + formatio "^1.2.0" + just-extend "^1.1.26" + lolex "^1.6.0" + path-to-regexp "^1.7.0" + text-encoding "^0.6.4" + +once@^1.3.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" + dependencies: + wrappy "1" + +os-tmpdir@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274" + +parserlib@~1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/parserlib/-/parserlib-1.1.1.tgz#a64cfa724062434fdfc351c9a4ec2d92b94c06f4" + +path-is-absolute@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" + +path-to-regexp@^1.7.0: + version "1.7.0" + resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-1.7.0.tgz#59fde0f435badacba103a84e9d3bc64e96b9937d" + dependencies: + isarray "0.0.1" + +pathval@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/pathval/-/pathval-1.1.0.tgz#b942e6d4bde653005ef6b71361def8727d0645e0" + +rimraf@~2.2.6: + version "2.2.8" + resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.2.8.tgz#e439be2aaee327321952730f99a8929e4fc50582" + +samsam@1.x: + version "1.3.0" + resolved "https://registry.yarnpkg.com/samsam/-/samsam-1.3.0.tgz#8d1d9350e25622da30de3e44ba692b5221ab7c50" + +sinon@^4.1.3: + version "4.1.3" + resolved "https://registry.yarnpkg.com/sinon/-/sinon-4.1.3.tgz#fc599eda47ed9f1a694ce774b94ab44260bd7ac5" + dependencies: + diff "^3.1.0" + formatio "1.2.0" + lodash.get "^4.4.2" + lolex "^2.2.0" + nise "^1.2.0" + supports-color "^4.4.0" + type-detect "^4.0.5" + +supports-color@4.4.0: + version "4.4.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-4.4.0.tgz#883f7ddabc165142b2a61427f3352ded195d1a3e" + dependencies: + has-flag "^2.0.0" + +supports-color@^4.4.0: + version "4.5.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-4.5.0.tgz#be7a0de484dec5c5cddf8b3d59125044912f635b" + dependencies: + has-flag "^2.0.0" + +temp@^0.8.3: + version "0.8.3" + resolved "https://registry.yarnpkg.com/temp/-/temp-0.8.3.tgz#e0c6bc4d26b903124410e4fed81103014dfc1f59" + dependencies: + os-tmpdir "^1.0.0" + rimraf "~2.2.6" + +text-encoding@^0.6.4: + version "0.6.4" + resolved "https://registry.yarnpkg.com/text-encoding/-/text-encoding-0.6.4.tgz#e399a982257a276dae428bb92845cb71bdc26d19" + +type-detect@^4.0.0, type-detect@^4.0.5: + version "4.0.5" + resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-4.0.5.tgz#d70e5bc81db6de2a381bcaca0c6e0cbdc7635de2" + +wrappy@1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" From 5899409507bceda9c6144f7c63281733f351018f Mon Sep 17 00:00:00 2001 From: Alexander Mankuta Date: Fri, 15 Dec 2017 16:55:30 +0200 Subject: [PATCH 42/46] Update engine to follow the Spec --- Dockerfile | 14 +++++++++++--- Makefile | 2 +- engine.json | 13 +++++++++++++ 3 files changed, 25 insertions(+), 4 deletions(-) create mode 100644 engine.json diff --git a/Dockerfile b/Dockerfile index b3c90a9..4f271fa 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,18 +1,26 @@ FROM node:alpine +LABEL maintainer="Code Climate " RUN adduser -u 9000 -D app WORKDIR /usr/src/app -COPY package.json yarn.lock ./ +COPY package.json yarn.lock engine.json ./ RUN yarn install && \ - chown -R app:app ./ + chown -R app:app ./ && \ + apk add --no-cache --virtual .dev-deps jq && \ + export csslint_version=$(yarn --json list --pattern csslint 2>/dev/null | jq -r '.data.trees[0].name' | cut -d@ -f2) && \ + cat engine.json | jq '.version = .version + "/" + env.csslint_version' > /engine.json && \ + apk del .dev-deps COPY . ./ +COPY . /usr/src/app + USER app -COPY . /usr/src/app +VOLUME /code +WORKDIR /code CMD ["/usr/src/app/bin/csslint"] diff --git a/Makefile b/Makefile index f83f495..43b56ef 100644 --- a/Makefile +++ b/Makefile @@ -4,4 +4,4 @@ image: docker build -t codeclimate/codeclimate-csslint . test: image - docker run --rm codeclimate/codeclimate-csslint npm run test + docker run --rm codeclimate/codeclimate-csslint sh -c "cd /usr/src/app && npm run test" diff --git a/engine.json b/engine.json new file mode 100644 index 0000000..6909387 --- /dev/null +++ b/engine.json @@ -0,0 +1,13 @@ +{ + "name": "CSSLint", + "description": "CSSLint is a tool to help point out problems with your CSS code.", + "maintainer": { + "name": "Code Climate", + "email": "hello@codeclimate.com" + }, + "languages": [ + "CSS" + ], + "version": "2.0.0", + "spec_version": "0.3.1" +} From c9e71a57a9f184b2a6fcf51e4057ff3cfba1f7db Mon Sep 17 00:00:00 2001 From: dantevvp Date: Wed, 8 Feb 2023 18:19:03 -0300 Subject: [PATCH 43/46] Push images to Dockerhub instead of GCR --- .circleci/config.yml | 53 ++++++++++++++++++++++++++++++++++++++++++++ Makefile | 10 ++++++++- circle.yml | 34 ---------------------------- 3 files changed, 62 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..164d05b --- /dev/null +++ b/.circleci/config.yml @@ -0,0 +1,53 @@ +version: 2.1 + +jobs: + build: + machine: + docker_layer_caching: true + working_directory: ~/codeclimate/codeclimate-csslint + 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-csslint + 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 43b56ef..4cddec1 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,15 @@ -.PHONY: test +.PHONY: image test release + +IMAGE_NAME ?= codeclimate/codeclimate-csslint +RELEASE_REGISTRY ?= codeclimate +RELEASE_TAG ?= latest image: docker build -t codeclimate/codeclimate-csslint . test: image docker run --rm codeclimate/codeclimate-csslint sh -c "cd /usr/src/app && npm run test" + +release: + docker tag $(IMAGE_NAME) $(RELEASE_REGISTRY)/codeclimate-csslint:$(RELEASE_TAG) + docker push $(RELEASE_REGISTRY)/codeclimate-csslint:$(RELEASE_TAG) diff --git a/circle.yml b/circle.yml deleted file mode 100644 index 43505ee..0000000 --- a/circle.yml +++ /dev/null @@ -1,34 +0,0 @@ -machine: - services: - - docker - -dependencies: - override: - - > - docker run - --env CIRCLE_BRANCH - --env CIRCLE_PROJECT_REPONAME - --env CIRCLE_TOKEN - --env GCR_JSON_KEY - --volume /var/run/docker.sock:/var/run/docker.sock - codeclimate/patrick pull || true - -test: - override: - - make test - -deployment: - registry: - branch: master - commands: - - > - docker run - --env CIRCLE_BUILD_NUM - --env CIRCLE_PROJECT_REPONAME - --env GCR_JSON_KEY - --volume /var/run/docker.sock:/var/run/docker.sock - codeclimate/patrick push gcr - -notify: - webhooks: - - url: https://cc-slack-proxy.herokuapp.com/circle From 84aaee335c4c8532d84e713cede3db43e7445a1c Mon Sep 17 00:00:00 2001 From: Dante Ventieri Date: Mon, 13 Feb 2023 12:42:08 -0300 Subject: [PATCH 44/46] override RELEASE_TAG on empty string --- Makefile | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 4cddec1..f3075a2 100644 --- a/Makefile +++ b/Makefile @@ -2,7 +2,10 @@ IMAGE_NAME ?= codeclimate/codeclimate-csslint RELEASE_REGISTRY ?= codeclimate -RELEASE_TAG ?= latest + +ifndef RELEASE_TAG +override RELEASE_TAG = latest +endif image: docker build -t codeclimate/codeclimate-csslint . From b6ccd2a459823f539b29535bb7fb8a748ccbd332 Mon Sep 17 00:00:00 2001 From: Laura Date: Fri, 28 Mar 2025 19:16:30 -0300 Subject: [PATCH 45/46] Update with deprecation notice.md --- README.md | 34 +++++++++++++++++++++++----------- 1 file changed, 23 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index ecb9ff6..163fdd3 100644 --- a/README.md +++ b/README.md @@ -1,19 +1,31 @@ -# Code Climate CSSLint Engine +# Try Qlty today, the newest edition of Code Climate Quality. +#### This repository is deprecated and archived. -[![Code Climate](https://codeclimate.com/repos/558419b6e30ba012290173f6/badges/85c90e6df38db8a9492d/gpa.svg)](https://codeclimate.com/repos/558419b6e30ba012290173f6/feed) +This is a repository for a Code Climate Quality plugin which is packaged as a Docker image. -`codeclimate-csslint` is a Code Climate engine that wraps [CSSLint](https://github.com/CSSLint/csslint). You can run it on your command line using the Code Climate CLI, or on our hosted analysis platform. +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. -CSSLint helps point out problems with your CSS code. It does basic syntax checking as well as applying a set of rules that look for problematic patterns or signs of inefficiency. Each rule is pluggable, so you can easily write your own or omit ones you don't want. +As a result, this repository is no longer maintained and has been archived. -### Installation +## Advantages of Qlty plugins +The new Qlty plugins system provides key advantages over the older, Docker-based plugin system: -1. If you haven't already, [install the Code Climate CLI](https://github.com/codeclimate/codeclimate). -2. Run `codeclimate engines:enable csslint`. 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`. +- 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 -### Need help? +## Try out Qlty today free -For help with CSSLint, [check out their documentation](https://github.com/CSSLint/csslint). +[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. -If you're running into a Code Climate issue, first look over this project's [GitHub Issues](https://github.com/codeclimate/codeclimate-csslint/issues), as your question may have already been covered. If not, [go ahead and open a support ticket with us](https://codeclimate.com/help). + - Install Qlty CLI: +` +curl https://qlty.sh | sh # Mac or Linux +` +or ` ` + +[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**: For existing customers of Quality, please see our [Migration Guide](https://docs.qlty.sh/migration/guide) for more information and resources. From 9b1d416546642e776bf3fe79894395732d51f18d Mon Sep 17 00:00:00 2001 From: Laura Date: Tue, 15 Apr 2025 17:22:29 -0300 Subject: [PATCH 46/46] 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)