├── .ruby-version ├── .codeclimate.yml ├── Gemfile ├── WERE_HIRING.md ├── bin └── csslint ├── Dockerfile ├── .rubocop.yml ├── Gemfile.lock ├── LICENSE ├── README.md ├── circle.yml ├── lib └── cc │ └── engine │ └── csslint.rb └── spec └── cc └── engine └── csslint_spec.rb /.ruby-version: -------------------------------------------------------------------------------- 1 | 2.2.3 2 | -------------------------------------------------------------------------------- /.codeclimate.yml: -------------------------------------------------------------------------------- 1 | engines: 2 | rubocop: 3 | enabled: true 4 | ratings: 5 | paths: 6 | - "**.rb" 7 | exclude_paths: 8 | - spec/**/* 9 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source "https://rubygems.org" 2 | 3 | gem 'json' 4 | gem 'nokogiri' 5 | gem "pry" 6 | 7 | group :test do 8 | gem "rake" 9 | gem "rspec" 10 | end 11 | -------------------------------------------------------------------------------- /WERE_HIRING.md: -------------------------------------------------------------------------------- 1 | # Code Climate is Hiring 2 | 3 | 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: 4 | 5 | #### http://jobs.codeclimate.com/ 6 | -------------------------------------------------------------------------------- /bin/csslint: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | $LOAD_PATH.unshift(File.expand_path(File.join(File.dirname(__FILE__), "../lib"))) 3 | 4 | require 'cc/engine/csslint' 5 | 6 | if File.exists?("/config.json") 7 | engine_config = JSON.parse(File.read("/config.json")) 8 | else 9 | engine_config = {} 10 | end 11 | 12 | CC::Engine::CSSlint.new( 13 | directory: "/code", engine_config: engine_config, io: STDOUT 14 | ).run 15 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM codeclimate/alpine-ruby:b38 2 | 3 | WORKDIR /usr/src/app 4 | COPY Gemfile /usr/src/app/ 5 | COPY Gemfile.lock /usr/src/app/ 6 | 7 | RUN apk --update add nodejs git ruby ruby-dev ruby-bundler less ruby-nokogiri build-base && \ 8 | bundle install -j 4 && \ 9 | apk del build-base && rm -fr /usr/share/ri 10 | 11 | RUN npm install -g codeclimate/csslint.git#7a3a6be 12 | 13 | RUN adduser -u 9000 -D app 14 | USER app 15 | 16 | COPY . /usr/src/app 17 | 18 | CMD ["/usr/src/app/bin/csslint"] 19 | -------------------------------------------------------------------------------- /.rubocop.yml: -------------------------------------------------------------------------------- 1 | Style/StringLiterals: 2 | Enabled: false 3 | 4 | Style/Documentation: 5 | Enabled: false 6 | 7 | Metrics/LineLength: 8 | Enabled: false 9 | 10 | Style/TrailingComma: 11 | Enabled: false 12 | 13 | Style/FileName: 14 | Exclude: 15 | - 'bin/**/*' 16 | 17 | Style/ClassAndModuleChildren: 18 | Exclude: 19 | - 'spec/**/*' 20 | 21 | Metrics/ModuleLength: 22 | Exclude: 23 | - 'spec/**/*' 24 | 25 | Style/GuardClause: 26 | Enabled: false 27 | 28 | Style/IfUnlessModifier: 29 | Enabled: false 30 | 31 | Style/DotPosition: 32 | Enabled: false 33 | 34 | Style/SignalException: 35 | Enabled: false 36 | 37 | Metrics/AbcSize: 38 | Enabled: false 39 | 40 | Rails/TimeZone: 41 | Enabled: false 42 | -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | GEM 2 | remote: https://rubygems.org/ 3 | specs: 4 | coderay (1.1.0) 5 | diff-lcs (1.2.5) 6 | json (1.8.3) 7 | method_source (0.8.2) 8 | mini_portile (0.6.2) 9 | nokogiri (1.6.6.2) 10 | mini_portile (~> 0.6.0) 11 | pry (0.10.1) 12 | coderay (~> 1.1.0) 13 | method_source (~> 0.8.1) 14 | slop (~> 3.4) 15 | rake (10.4.2) 16 | rspec (3.2.0) 17 | rspec-core (~> 3.2.0) 18 | rspec-expectations (~> 3.2.0) 19 | rspec-mocks (~> 3.2.0) 20 | rspec-core (3.2.3) 21 | rspec-support (~> 3.2.0) 22 | rspec-expectations (3.2.1) 23 | diff-lcs (>= 1.2.0, < 2.0) 24 | rspec-support (~> 3.2.0) 25 | rspec-mocks (3.2.1) 26 | diff-lcs (>= 1.2.0, < 2.0) 27 | rspec-support (~> 3.2.0) 28 | rspec-support (3.2.2) 29 | slop (3.6.0) 30 | 31 | PLATFORMS 32 | ruby 33 | 34 | DEPENDENCIES 35 | json 36 | nokogiri 37 | pry 38 | rake 39 | rspec 40 | 41 | BUNDLED WITH 42 | 1.10.6 43 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015 Code Climate, Inc. 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Code Climate CSSLint Engine 2 | 3 | [![Code Climate](https://codeclimate.com/repos/558419b6e30ba012290173f6/badges/85c90e6df38db8a9492d/gpa.svg)](https://codeclimate.com/repos/558419b6e30ba012290173f6/feed) 4 | 5 | `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. 6 | 7 | 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. 8 | 9 | ### Installation 10 | 11 | 1. If you haven't already, [install the Code Climate CLI](https://github.com/codeclimate/codeclimate). 12 | 2. Run `codeclimate engines:enable csslint`. This command both installs the engine and enables it in your `.codeclimate.yml` file. 13 | 3. You're ready to analyze! Browse into your project's folder and run `codeclimate analyze`. 14 | 15 | ### Need help? 16 | 17 | For help with CSSLint, [check out their documentation](https://github.com/CSSLint/csslint). 18 | 19 | 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). 20 | -------------------------------------------------------------------------------- /circle.yml: -------------------------------------------------------------------------------- 1 | machine: 2 | services: 3 | - docker 4 | environment: 5 | CLOUDSDK_CORE_DISABLE_PROMPTS: 1 6 | CODECLIMATE_DOCKER_REGISTRY_HOSTNAME: registry.codeclimate.net 7 | CODECLIMATE_DOCKER_REGISTRY_USERNAME: circleci 8 | CODECLIMATE_DOCKER_REGISTRY_EMAIL: ops@codeclimate.com 9 | 10 | dependencies: 11 | override: 12 | - echo $gcloud_json_key_base64 | sed 's/ //g' | base64 -d > /tmp/gcloud_key.json 13 | - curl https://sdk.cloud.google.com | bash 14 | - bundle install 15 | - npm install -g codeclimate/csslint.git#7a3a6be 16 | 17 | test: 18 | override: 19 | - bundle exec rspec spec 20 | - docker build -t=$registry_root/$CIRCLE_PROJECT_REPONAME:b$CIRCLE_BUILD_NUM . 21 | 22 | deployment: 23 | registry: 24 | branch: master 25 | commands: 26 | - gcloud auth activate-service-account --key-file /tmp/gcloud_key.json 27 | - gcloud docker -a 28 | - docker push $registry_root/$CIRCLE_PROJECT_REPONAME:b$CIRCLE_BUILD_NUM 29 | - docker login --username=$CODECLIMATE_DOCKER_REGISTRY_USERNAME --password=$CODECLIMATE_DOCKER_REGISTRY_PASSWORD --email=$CODECLIMATE_DOCKER_REGISTRY_EMAIL $CODECLIMATE_DOCKER_REGISTRY_HOSTNAME 30 | - 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 31 | - docker push $CODECLIMATE_DOCKER_REGISTRY_HOSTNAME/$CIRCLE_PROJECT_USERNAME/$CIRCLE_PROJECT_REPONAME:b$CIRCLE_BUILD_NUM 32 | 33 | -------------------------------------------------------------------------------- /lib/cc/engine/csslint.rb: -------------------------------------------------------------------------------- 1 | require 'nokogiri' 2 | require 'json' 3 | 4 | module CC 5 | module Engine 6 | class CSSlint 7 | def initialize(directory: , io: , engine_config: ) 8 | @directory = directory 9 | @engine_config = engine_config 10 | @io = io 11 | end 12 | 13 | def run 14 | Dir.chdir(@directory) do 15 | results.xpath('//file').each do |file| 16 | path = file['name'].sub(/\A#{@directory}\//, '') 17 | file.xpath('//error').each do |lint| 18 | issue = { 19 | type: "issue", 20 | check_name: lint["source"], 21 | description: lint["message"], 22 | categories: ["Style"], 23 | remediation_points: 500, 24 | location: { 25 | path: path, 26 | positions: { 27 | begin: { 28 | line: lint["line"].to_i, 29 | column: lint["column"].to_i 30 | }, 31 | end: { 32 | line: lint["line"].to_i, 33 | column: lint["column"].to_i 34 | } 35 | } 36 | } 37 | } 38 | 39 | puts("#{issue.to_json}\0") 40 | end 41 | end 42 | end 43 | end 44 | 45 | private 46 | 47 | def results 48 | @results ||= Nokogiri::XML(csslint_xml) 49 | end 50 | 51 | def build_files_with_exclusions(exclusions) 52 | files = Dir.glob("**/*.css") 53 | files.reject { |f| exclusions.include?(f) } 54 | end 55 | 56 | def build_files_with_inclusions(inclusions) 57 | inclusions.map do |include_path| 58 | if include_path =~ %r{/$} 59 | Dir.glob("#{include_path}/**/*.css") 60 | else 61 | include_path if include_path =~ /\.css$/ 62 | end 63 | end.flatten.compact 64 | end 65 | 66 | def csslint_xml 67 | `csslint --format=checkstyle-xml #{files_to_inspect.join(" ")}` 68 | end 69 | 70 | def files_to_inspect 71 | if @engine_config["include_paths"] 72 | build_files_with_inclusions(@engine_config["include_paths"]) 73 | else 74 | build_files_with_exclusions(@engine_config["exclude_paths"] || []) 75 | end 76 | end 77 | end 78 | end 79 | end 80 | -------------------------------------------------------------------------------- /spec/cc/engine/csslint_spec.rb: -------------------------------------------------------------------------------- 1 | require 'cc/engine/csslint' 2 | require 'tmpdir' 3 | 4 | module CC 5 | module Engine 6 | describe CSSlint do 7 | let(:code) { Dir.mktmpdir } 8 | let(:engine_config) { {} } 9 | let(:lint) do 10 | CSSlint.new(directory: code, io: nil, engine_config: engine_config) 11 | end 12 | let(:id_selector_content) { '#id { color: red; }' } 13 | 14 | describe '#run' do 15 | it 'analyzes *.css files' do 16 | create_source_file('foo.css', id_selector_content) 17 | expect{ lint.run }.to output(/Don't use IDs in selectors./).to_stdout 18 | end 19 | 20 | it "doesn't analyze *.scss files" do 21 | create_source_file('foo.scss', id_selector_content) 22 | expect{ lint.run }.to_not output.to_stdout 23 | end 24 | 25 | describe "with exclude_paths" do 26 | let(:engine_config) { {"exclude_paths" => %w(excluded.css)} } 27 | 28 | before do 29 | create_source_file("not_excluded.css", "p { margin: 5px }") 30 | create_source_file("excluded.css", id_selector_content) 31 | end 32 | 33 | it "excludes all matching paths" do 34 | expect{ lint.run }.not_to \ 35 | output(/Don't use IDs in selectors./).to_stdout 36 | end 37 | end 38 | 39 | describe "with include_paths" do 40 | let(:engine_config) { 41 | {"include_paths" => %w(included.css included_dir/ config.yml)} 42 | } 43 | 44 | before do 45 | create_source_file("included.css", id_selector_content) 46 | create_source_file( 47 | "included_dir/file.css", "p { color: blue !important; }" 48 | ) 49 | create_source_file( 50 | "included_dir/sub/sub/subdir/file.css", "img { }" 51 | ) 52 | create_source_file("config.yml", "foo:\n bar: \"baz\"") 53 | create_source_file("not_included.css", "a { outline: none; }") 54 | end 55 | 56 | it "includes all mentioned files" do 57 | expect{ lint.run }.to \ 58 | output(/Don't use IDs in selectors./).to_stdout 59 | end 60 | 61 | it "expands directories" do 62 | expect{ lint.run }.to output(/Use of !important/).to_stdout 63 | expect{ lint.run }.to output(/Rule is empty/).to_stdout 64 | end 65 | 66 | it "excludes any unmentioned files" do 67 | expect{ lint.run }.not_to \ 68 | output(/Outlines should only be modified using :focus/).to_stdout 69 | end 70 | 71 | it "shouldn't call a top-level Dir.glob ever" do 72 | expect(Dir).not_to receive(:glob).with("**/*.css") 73 | expect{ lint.run }.to \ 74 | output(/Don't use IDs in selectors./).to_stdout 75 | end 76 | 77 | it "only includes CSS files, even when a non-CSS file is directly included" do 78 | expect{ lint.run }.not_to output(/config.yml/).to_stdout 79 | end 80 | end 81 | end 82 | 83 | def create_source_file(path, content) 84 | abs_path = File.join(code, path) 85 | FileUtils.mkdir_p(File.dirname(abs_path)) 86 | File.write(abs_path, content) 87 | end 88 | end 89 | end 90 | end 91 | --------------------------------------------------------------------------------