├── .gitignore ├── spec ├── fixtures │ ├── files │ │ ├── failure.sass │ │ ├── ignored.sass │ │ ├── failure.scss │ │ ├── ignored.scss │ │ ├── pass.sass │ │ └── pass.scss │ └── config │ │ ├── .relative-config.yml │ │ └── .sass-lint.yml ├── .eslintrc ├── linter-sass-lint-path-options-spec.js ├── linter-sass-lint-resolve-spec.js ├── helpers-spec.js ├── linter-sass-lint-sass-spec.js └── linter-sass-lint-scss-spec.js ├── .gitattributes ├── lib ├── constants.js ├── helpers.js └── main.js ├── .editorconfig ├── PULL_REQUEST_TEMPLATE.md ├── appveyor.yml ├── .travis.yml ├── coffeelint.json ├── ISSUE_TEMPLATE.md ├── LICENSE ├── CONTRIBUTING.md ├── package.json ├── .circleci └── config.yml ├── README.md └── CHANGELOG.md /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | npm-debug.log 3 | node_modules 4 | -------------------------------------------------------------------------------- /spec/fixtures/files/failure.sass: -------------------------------------------------------------------------------- 1 | #test 2 | color: red 3 | -------------------------------------------------------------------------------- /spec/fixtures/files/ignored.sass: -------------------------------------------------------------------------------- 1 | #test 2 | color: red 3 | -------------------------------------------------------------------------------- /spec/fixtures/files/failure.scss: -------------------------------------------------------------------------------- 1 | #test { 2 | color: red; 3 | } 4 | -------------------------------------------------------------------------------- /spec/fixtures/files/ignored.scss: -------------------------------------------------------------------------------- 1 | #test { 2 | color: red; 3 | } 4 | -------------------------------------------------------------------------------- /spec/fixtures/files/pass.sass: -------------------------------------------------------------------------------- 1 | $var-red: red 2 | 3 | .test 4 | color: $red 5 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text eol=lf 3 | -------------------------------------------------------------------------------- /spec/fixtures/files/pass.scss: -------------------------------------------------------------------------------- 1 | $var-red: red; 2 | 3 | .test { 4 | color: $red; 5 | } 6 | -------------------------------------------------------------------------------- /lib/constants.js: -------------------------------------------------------------------------------- 1 | 'use babel'; 2 | 3 | export default { 4 | SASSLINT_DOC_URL: 'https://github.com/sasstools/sass-lint/tree/master/docs/rules', 5 | VALID_SYNTAXES: ['scss', 'sass'], 6 | }; 7 | -------------------------------------------------------------------------------- /spec/fixtures/config/.relative-config.yml: -------------------------------------------------------------------------------- 1 | options: 2 | formatter: json 3 | merge-default-rules: false 4 | files: 5 | include: '../files/ignored.scss' 6 | rules: 7 | no-color-literals: 1 8 | no-ids: 2 9 | -------------------------------------------------------------------------------- /spec/fixtures/config/.sass-lint.yml: -------------------------------------------------------------------------------- 1 | options: 2 | formatter: json 3 | merge-default-rules: false 4 | files: 5 | include: '**/*.s+(a|c)ss' 6 | ignore: '**/ignored.s+(a|c)ss' 7 | rules: 8 | no-color-literals: 1 9 | no-ids: 2 10 | -------------------------------------------------------------------------------- /spec/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "atomtest": true, 4 | "jasmine": true 5 | }, 6 | "rules": { 7 | "import/no-extraneous-dependencies": [ 8 | "error", 9 | { 10 | "devDependencies": true 11 | } 12 | ] 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig helps developers define and maintain consistent 2 | # coding styles between different editors and IDEs 3 | # editorconfig.org 4 | 5 | root = true 6 | 7 | [*] 8 | indent_style = space 9 | indent_size = 2 10 | end_of_line = lf 11 | charset = utf-8 12 | trim_trailing_whitespace = true 13 | insert_final_newline = true 14 | 15 | [*.md] 16 | trim_trailing_whitespace = false 17 | -------------------------------------------------------------------------------- /PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ## New Pull Request Information 2 | > Please make sure you have read through our [contribution guidelines](CONTRIBUTING.md) before submitting a pull request. 3 | Most importantly your pull request should provide information on what the changes do and they should reference an already created issue. e.g. 'Fixes #80' 4 | 5 | ### PR checklist 6 | 7 | > Please delete everything above this line when submitting your PR. 8 | 9 | --- 10 | 11 | - Expected behaviour: 12 | - Issue number this PR resolves: 13 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | ### Project specific config ### 2 | environment: 3 | APM_TEST_PACKAGES: 4 | ATOM_LINT_WITH_BUNDLED_NODE: "false" 5 | 6 | matrix: 7 | - ATOM_CHANNEL: stable 8 | - ATOM_CHANNEL: beta 9 | 10 | install: 11 | # Install Node.js to run any configured linters 12 | - ps: Install-Product node 10 13 | - npm install -g sass-lint 14 | 15 | ### Generic setup follows ### 16 | build_script: 17 | - ps: iex ((new-object net.webclient).DownloadString('https://raw.githubusercontent.com/atom/ci/master/build-package.ps1')) 18 | 19 | branches: 20 | only: 21 | - master 22 | 23 | version: "{build}" 24 | platform: x64 25 | clone_depth: 10 26 | skip_tags: true 27 | test: off 28 | deploy: off 29 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | ### Project specific config ### 2 | env: 3 | global: 4 | - ATOM_LINT_WITH_BUNDLED_NODE="false" 5 | 6 | os: 7 | - osx 8 | 9 | # Installed for linting the project 10 | language: node_js 11 | node_js: "10" 12 | 13 | install: 14 | - npm install -g sass-lint 15 | 16 | ### Generic setup follows ### 17 | script: 'curl -s https://raw.githubusercontent.com/atom/ci/master/build-package.sh | sh' 18 | 19 | notifications: 20 | email: 21 | on_success: never 22 | on_failure: change 23 | 24 | branches: 25 | only: 26 | - master 27 | 28 | git: 29 | depth: 10 30 | 31 | sudo: false 32 | 33 | addons: 34 | apt: 35 | packages: 36 | - build-essential 37 | - git 38 | - libgnome-keyring-dev 39 | - fakeroot 40 | -------------------------------------------------------------------------------- /coffeelint.json: -------------------------------------------------------------------------------- 1 | { 2 | "max_line_length": { 3 | "level": "ignore" 4 | }, 5 | "no_empty_param_list": { 6 | "level": "error" 7 | }, 8 | "arrow_spacing": { 9 | "level": "error" 10 | }, 11 | "no_interpolation_in_single_quotes": { 12 | "level": "error" 13 | }, 14 | "no_debugger": { 15 | "level": "error" 16 | }, 17 | "prefer_english_operator": { 18 | "level": "error" 19 | }, 20 | "colon_assignment_spacing": { 21 | "spacing": { 22 | "left": 0, 23 | "right": 1 24 | }, 25 | "level": "error" 26 | }, 27 | "braces_spacing": { 28 | "spaces": 0, 29 | "level": "error" 30 | }, 31 | "spacing_after_comma": { 32 | "level": "error" 33 | }, 34 | "no_stand_alone_at": { 35 | "level": "error" 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ## New Issue information 2 | > If your issue is a feature request then please try to provide as much information on the expected 3 | behaviour as possible. 4 | 5 | > If you're submitting a bug then please make sure you include all the information requested in the 6 | checklist below 7 | 8 | > If the problem you're seeing is to do with the linter reporting incorrect linting issues or 9 | anything to do with sass-lint rules then you would be better off posting your issue over at the 10 | sass-lint repo https://github.com/sasstools/sass-lint/issues 11 | 12 | ### Issue checklist 13 | 14 | > Please delete everything above this line when submitting your issue. 15 | 16 | --- 17 | 18 | 19 | 20 | - Atom version: 21 | - linter-sass-lint version: 22 | - Global sass-lint version (if used): 23 | - Platform: 24 | - [ ] OSX 25 | - [ ] Windows 26 | - [ ] Linux 27 | - Expected behaviour: 28 | - Actual behaviour / issue description: 29 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Dan Purdy 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /spec/linter-sass-lint-path-options-spec.js: -------------------------------------------------------------------------------- 1 | 'use babel'; 2 | 3 | import { 4 | // eslint-disable-next-line no-unused-vars 5 | it, fit, wait, beforeEach, afterEach, 6 | } from 'jasmine-fix'; 7 | import { join } from 'path'; 8 | 9 | const { lint } = require('../lib/main').provideLinter(); 10 | 11 | const failurePath = join(__dirname, 'fixtures', 'files', 'failure.scss'); 12 | const configFile = join(__dirname, 'fixtures', 'config', '.sass-lint.yml'); 13 | 14 | describe('The sass-lint provider for Linter - path options', () => { 15 | beforeEach(async () => { 16 | atom.workspace.destroyActivePaneItem(); 17 | await atom.packages.activatePackage('language-sass'); 18 | await atom.packages.activatePackage('linter-sass-lint'); 19 | }); 20 | 21 | describe('checks failure.scss, expects a message and', () => { 22 | let messages = null; 23 | 24 | beforeEach(async () => { 25 | atom.config.set('linter-sass-lint.configFile', configFile); 26 | atom.config.set('linter-sass-lint.globalSassLint', true); 27 | const editor = await atom.workspace.open(failurePath); 28 | messages = await lint(editor); 29 | }); 30 | 31 | it('lints the file with the globally installed sass-lint', () => { 32 | expect(messages.length).toBeGreaterThan(0); 33 | }); 34 | 35 | it('verifies the first message', () => { 36 | const slDocUrl = 'https://github.com/sasstools/sass-lint/tree/master/docs/rules/no-ids.md'; 37 | const warnId = 'ID selectors not allowed (no-ids)'; 38 | 39 | expect(messages[0].severity).toBe('error'); 40 | expect(messages[0].description).not.toBeDefined(); 41 | expect(messages[0].url).toBe(slDocUrl); 42 | expect(messages[0].excerpt).toBe(warnId); 43 | expect(messages[0].location.file).toBe(failurePath); 44 | expect(messages[0].location.position).toEqual([[0, 0], [0, 5]]); 45 | }); 46 | 47 | it('verifies the second message', () => { 48 | const slDocUrl = 'https://github.com/sasstools/sass-lint/tree/master/docs/rules/no-color-literals.md'; 49 | const warnId = 'Color literals such as \'red\' should only be used in variable declarations (no-color-literals)'; 50 | 51 | expect(messages[1].severity).toBe('warning'); 52 | expect(messages[1].description).not.toBeDefined(); 53 | expect(messages[1].url).toBe(slDocUrl); 54 | expect(messages[1].excerpt).toBe(warnId); 55 | expect(messages[1].location.file).toBe(failurePath); 56 | expect(messages[1].location.position).toEqual([[1, 9], [1, 12]]); 57 | }); 58 | }); 59 | }); 60 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | ## Submitting Issues 2 | 3 | * Before creating a new issue, perform a cursory search to see if a similar issue has already been submitted. 4 | * You can create an issue [here](https://github.com/AtomLinter/linter-sass-lint/issues). Please include as many details as possible when filing an issue. 5 | * Issue titles should be descriptive, explaining at the high level what it is about. 6 | * Please include the version of linter-sass-lint being used and your Atom version 7 | * Please include full error messages/output when submitting issues where appropriate. 8 | 9 | ## Pull Requests 10 | 11 | * Pull requests should reference their related issues where possible. If the pull request closes an issue, [please reference its closing from a commit messages](https://help.github.com/articles/closing-issues-via-commit-messages/). 12 | * Pull request titles should be descriptive, explaining at the high level what it is doing, and should be written in the same style as [Git commit messages](#git-commit-messages). 13 | * Follow the current coding style. Your changes should pass `npm run lint`. 14 | * Ensure that [EditorConfig](http://editorconfig.org/) installed in the editor and that it is functioning properly. 15 | * Do not squash or rebase commits when submitting a Pull Request. It makes it much harder to follow work and make incremental changes. 16 | * Ensure no Emoji tags are used in the title of the Pull Request 17 | 18 | ### Git Commit Messages 19 | 20 | * Use the present tense (`"Add feature"` not `"Added Feature"`) 21 | * Use the imperative mood (`"Move cursor to…"` not `"Moves cursor to…"`) 22 | * Limit the first line to 72 characters or less 23 | * Include relevant Emoji from our [Emoji cheatsheet](#emoji-cheatsheet) 24 | 25 | ## Emoji Cheatsheet 26 | 27 | When creating creating commits or updating the CHANGELOG, please **start** the commit message or update with one of the following applicable Emoji. Emoji should not be used at the start of issue or pull request titles. 28 | 29 | * :art: `:art:` when improving the format/structure of the code 30 | * :racehorse: `:racehorse:` when improving performance 31 | * :mag: `:mag:` when adding a rule 32 | * :memo: `:memo:` when writing long-form text (documentation, CHANGELOG, README, etc…) 33 | * :bug: `:bug:` when fixing a bug 34 | * :fire: `:fire:` when removing code or files 35 | * :green_heart: `:green_heart:` when fixing the CI build 36 | * :white_check_mark: `:white_check_mark:` when adding tests 37 | * :lock: `:lock:` when dealing with security 38 | * :arrow_up: `:arrow_up:` when upgrading dependencies 39 | * :arrow_down: `:arrow_down:` when downgrading dependencies 40 | * :shirt: `:shirt:` when removing linter warnings 41 | * :shipit: `:shipit:` when creating a new release 42 | ˜ 43 | -------------------------------------------------------------------------------- /spec/linter-sass-lint-resolve-spec.js: -------------------------------------------------------------------------------- 1 | 'use babel'; 2 | 3 | import { 4 | // eslint-disable-next-line no-unused-vars 5 | it, fit, wait, beforeEach, afterEach, 6 | } from 'jasmine-fix'; 7 | import { join } from 'path'; 8 | 9 | const { lint } = require('../lib/main').provideLinter(); 10 | 11 | const failurePath = join(__dirname, 'fixtures', 'files', 'failure.scss'); 12 | const ignoredPath = join(__dirname, 'fixtures', 'files', 'ignored.scss'); 13 | const configFile = join(__dirname, 'fixtures', 'config', '.relative-config.yml'); 14 | 15 | describe('The sass-lint provider for Linter - resolve paths relative to config file', () => { 16 | beforeEach(async () => { 17 | atom.workspace.destroyActivePaneItem(); 18 | await atom.packages.activatePackage('language-sass'); 19 | await atom.packages.activatePackage('linter-sass-lint'); 20 | }); 21 | 22 | describe('checks ignored.scss and', () => { 23 | let messages = null; 24 | 25 | beforeEach(async () => { 26 | atom.config.set('linter-sass-lint.configFile', configFile); 27 | atom.config.set('linter-sass-lint.resolvePathsRelativeToConfig', true); 28 | const editor = await atom.workspace.open(ignoredPath); 29 | messages = await lint(editor); 30 | }); 31 | 32 | it('finds at least one message', () => { 33 | expect(messages.length).toBeGreaterThan(0); 34 | }); 35 | 36 | it('verifies the first message', () => { 37 | const slDocUrl = 'https://github.com/sasstools/sass-lint/tree/master/docs/rules/no-ids.md'; 38 | const warnId = 'ID selectors not allowed (no-ids)'; 39 | 40 | expect(messages[0].severity).toBe('error'); 41 | expect(messages[0].description).not.toBeDefined(); 42 | expect(messages[0].url).toBe(slDocUrl); 43 | expect(messages[0].excerpt).toBe(warnId); 44 | expect(messages[0].location.file).toBe(ignoredPath); 45 | expect(messages[0].location.position).toEqual([[0, 0], [0, 5]]); 46 | }); 47 | 48 | it('verifies the second message', () => { 49 | const slDocUrl = 'https://github.com/sasstools/sass-lint/tree/master/docs/rules/no-color-literals.md'; 50 | const warnId = 'Color literals such as \'red\' should only be used in variable declarations (no-color-literals)'; 51 | 52 | expect(messages[1].severity).toBe('warning'); 53 | expect(messages[1].description).not.toBeDefined(); 54 | expect(messages[1].url).toBe(slDocUrl); 55 | expect(messages[1].excerpt).toBe(warnId); 56 | expect(messages[1].location.file).toBe(ignoredPath); 57 | expect(messages[1].location.position).toEqual([[1, 9], [1, 12]]); 58 | }); 59 | }); 60 | 61 | describe('checks failure.scss and', () => { 62 | it('finds nothing wrong with the valid file', async () => { 63 | atom.config.set('linter-sass-lint.configFile', configFile); 64 | const editor = await atom.workspace.open(failurePath); 65 | const messages = await lint(editor); 66 | 67 | expect(messages.length).toBe(0); 68 | }); 69 | }); 70 | }); 71 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "linter-sass-lint", 3 | "main": "./lib/main", 4 | "version": "1.9.0", 5 | "description": "Atom Linter plugin to lint your Sass/SCSS with pure Node.js sass-lint", 6 | "repository": "https://github.com/AtomLinter/linter-sass-lint", 7 | "license": "MIT", 8 | "keywords": [ 9 | "Sass", 10 | "scss", 11 | "sass-lint", 12 | "node-sass", 13 | "linter" 14 | ], 15 | "scripts": { 16 | "lint": "eslint ." 17 | }, 18 | "engines": { 19 | "atom": ">=1.0.0 <2.0.0" 20 | }, 21 | "dependencies": { 22 | "atom-linter": "10.0.0", 23 | "atom-package-deps": "5.1.0", 24 | "consistent-env": "1.3.1", 25 | "globule": "1.3.1", 26 | "resolve": "1.15.1", 27 | "sass-lint": "1.13.1" 28 | }, 29 | "package-deps": [ 30 | "linter:2.0.0" 31 | ], 32 | "providedServices": { 33 | "linter": { 34 | "versions": { 35 | "2.0.0": "provideLinter" 36 | } 37 | } 38 | }, 39 | "configSchema": { 40 | "noConfigDisable": { 41 | "title": "Disable when no sass-lint config file is found in your project root", 42 | "type": "boolean", 43 | "description": "and a .sass-lint.yml file is not specified in the .sass-lint.yml Path option", 44 | "default": false 45 | }, 46 | "resolvePathsRelativeToConfig": { 47 | "title": "Resolve paths in configuration relative to config file", 48 | "type": "boolean", 49 | "description": "Instead of the default where paths are resolved relative to the project root", 50 | "default": false 51 | }, 52 | "configFile": { 53 | "title": ".sass-lint.yml Config File", 54 | "description": "A .sass-lint.yml file to use/fallback to if no config file is found in the \"current\" project root", 55 | "type": "string", 56 | "default": "" 57 | }, 58 | "globalNodePath": { 59 | "title": "Global Node Installation Path", 60 | "description": "Run `npm get prefix` and paste the result here", 61 | "type": "string", 62 | "default": "" 63 | }, 64 | "globalSassLint": { 65 | "title": "Use global sass-lint installation", 66 | "description": "The latest sass-lint is included in this package but if you'd like to use a globally installed one enable it here.\nMake sure sass-lint is installed globally and is in your `$PATH`", 67 | "type": "boolean", 68 | "default": false 69 | } 70 | }, 71 | "devDependencies": { 72 | "eslint": "6.8.0", 73 | "eslint-config-airbnb-base": "14.0.0", 74 | "eslint-plugin-import": "2.20.1", 75 | "jasmine-fix": "1.3.1" 76 | }, 77 | "renovate": { 78 | "extends": [ 79 | "config:base" 80 | ] 81 | }, 82 | "eslintConfig": { 83 | "rules": { 84 | "global-require": "off", 85 | "import/no-unresolved": [ 86 | "error", 87 | { 88 | "ignore": [ 89 | "atom" 90 | ] 91 | } 92 | ] 93 | }, 94 | "extends": "airbnb-base", 95 | "globals": { 96 | "atom": true 97 | }, 98 | "env": { 99 | "node": true, 100 | "browser": true 101 | } 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /spec/helpers-spec.js: -------------------------------------------------------------------------------- 1 | 'use babel'; 2 | 3 | import { 4 | // eslint-disable-next-line no-unused-vars 5 | it, fit, wait, beforeEach, afterEach, 6 | } from 'jasmine-fix'; 7 | // NOTE: This rule appears to currently be broken 8 | // eslint-disable-next-line import/named 9 | import { SASSLINT_DOC_URL } from '../lib/constants'; 10 | 11 | const fs = require('fs'); 12 | const helpers = require('../lib/helpers'); 13 | 14 | describe('helpers', () => { 15 | describe('getRuleURI', () => { 16 | it('should return the correct rule URL', () => { 17 | const ruleId = 'no-ids'; 18 | const result = helpers.getRuleURI(ruleId); 19 | 20 | expect(result).toEqual(`${SASSLINT_DOC_URL}/${ruleId}.md`); 21 | }); 22 | }); 23 | 24 | describe('isValidSyntax', () => { 25 | it('should return true if a supported syntax is passed', () => { 26 | expect(helpers.isValidSyntax('scss')).toBe(true); 27 | }); 28 | 29 | it('should return false if a supported syntax is not passed', () => { 30 | expect(helpers.isValidSyntax('html')).toBe(false); 31 | }); 32 | }); 33 | 34 | describe('getFileSyntax', () => { 35 | it('it should return scss if a scss filename is provided', () => { 36 | expect(helpers.getFileSyntax('test/file.scss')).toBe('scss'); 37 | }); 38 | 39 | it('it should return sass if a sass filename is provided', () => { 40 | expect(helpers.getFileSyntax('test/file.sass')).toBe('sass'); 41 | }); 42 | 43 | it('it should return scss if a scss.liquid filename is provided', () => { 44 | expect(helpers.getFileSyntax('test/file.scss.liquid')).toBe('scss'); 45 | }); 46 | 47 | it('it should return sass if a sass.liquid filename is provided', () => { 48 | expect(helpers.getFileSyntax('test/file.sass.liquid')).toBe('sass'); 49 | }); 50 | 51 | it('it should return html if a html filename is provided', () => { 52 | expect(helpers.getFileSyntax('test/file.html')).toBe('html'); 53 | }); 54 | }); 55 | 56 | describe('getRootDir', () => { 57 | let editor = null; 58 | 59 | beforeEach(async () => { 60 | editor = await atom.workspace.open(`${__dirname}/fixtures/files/failure.scss`); 61 | }); 62 | 63 | it('should return null if the file isn\'t within the currently open project', () => { 64 | expect(helpers.getRootDir('/test.scss')).toEqual(null); 65 | }); 66 | 67 | it( 68 | 'should return the root dir object if the file is part of the currently open project', 69 | () => { 70 | expect(helpers.getRootDir(editor.getPath())).not.toEqual(null); 71 | expect(helpers.getRootDir(editor.getPath())).toBeDefined(); 72 | }, 73 | ); 74 | }); 75 | 76 | describe('getRootDirConfig', () => { 77 | let editor = null; 78 | 79 | beforeEach(async () => { 80 | editor = await atom.workspace.open(`${__dirname}/fixtures/files/failure.scss`); 81 | }); 82 | 83 | it('should return null if no root directory is specified', () => { 84 | expect(helpers.getRootDirConfig(null, '.sass-lint.yml')).toBe(null); 85 | }); 86 | 87 | it('should return null if no config exists in the root of the project', () => { 88 | const rootDir = helpers.getRootDir(editor.getPath()); 89 | expect(helpers.getRootDirConfig(rootDir, '.sass-lint.yml')).toBe(null); 90 | }); 91 | 92 | 93 | it('should return the config file path if a config is found in the project root', () => { 94 | spyOn(fs, 'accessSync').andReturn(true); 95 | const rootDir = helpers.getRootDir(editor.getPath()); 96 | expect(helpers.getRootDirConfig(rootDir, '.sass-lint.yml')).not.toBe(null); 97 | expect(fs.accessSync).toHaveBeenCalled(); 98 | }); 99 | }); 100 | }); 101 | -------------------------------------------------------------------------------- /spec/linter-sass-lint-sass-spec.js: -------------------------------------------------------------------------------- 1 | 'use babel'; 2 | 3 | import { 4 | // eslint-disable-next-line no-unused-vars 5 | it, fit, wait, beforeEach, afterEach, 6 | } from 'jasmine-fix'; 7 | import { join } from 'path'; 8 | 9 | const { lint } = require('../lib/main').provideLinter(); 10 | 11 | const failurePath = join(__dirname, 'fixtures', 'files', 'failure.scss'); 12 | const ignoredPath = join(__dirname, 'fixtures', 'files', 'ignored.scss'); 13 | const passPath = join(__dirname, 'fixtures', 'files', 'pass.scss'); 14 | const configFile = join(__dirname, 'fixtures', 'config', '.sass-lint.yml'); 15 | 16 | describe('The sass-lint provider for Linter - sass', () => { 17 | beforeEach(async () => { 18 | atom.workspace.destroyActivePaneItem(); 19 | await atom.packages.activatePackage('language-sass'); 20 | await atom.packages.activatePackage('linter-sass-lint'); 21 | }); 22 | 23 | describe('checks failure.sass and', () => { 24 | let messages = null; 25 | 26 | beforeEach(async () => { 27 | atom.config.set('linter-sass-lint.configFile', configFile); 28 | const editor = await atom.workspace.open(failurePath); 29 | messages = await lint(editor); 30 | }); 31 | 32 | it('finds at least one message', () => { 33 | expect(messages.length).toBeGreaterThan(0); 34 | }); 35 | 36 | it('verifies the first message', () => { 37 | const slDocUrl = 'https://github.com/sasstools/sass-lint/tree/master/docs/rules/no-ids.md'; 38 | const warnId = 'ID selectors not allowed (no-ids)'; 39 | 40 | expect(messages[0].severity).toBe('error'); 41 | expect(messages[0].description).not.toBeDefined(); 42 | expect(messages[0].url).toBe(slDocUrl); 43 | expect(messages[0].excerpt).toBe(warnId); 44 | expect(messages[0].location.file).toBe(failurePath); 45 | expect(messages[0].location.position).toEqual([[0, 0], [0, 5]]); 46 | }); 47 | 48 | it('verifies the second message', () => { 49 | const slDocUrl = 'https://github.com/sasstools/sass-lint/tree/master/docs/rules/no-color-literals.md'; 50 | const warnId = 'Color literals such as \'red\' should only be used in variable declarations (no-color-literals)'; 51 | 52 | expect(messages[1].severity).toBe('warning'); 53 | expect(messages[1].description).not.toBeDefined(); 54 | expect(messages[1].url).toBe(slDocUrl); 55 | expect(messages[1].excerpt).toBe(warnId); 56 | expect(messages[1].location.file).toBe(failurePath); 57 | expect(messages[1].location.position).toEqual([[1, 9], [1, 12]]); 58 | }); 59 | }); 60 | 61 | describe('checks pass.sass and', () => { 62 | it('finds nothing wrong with the valid file', async () => { 63 | atom.config.set('linter-sass-lint.configFile', configFile); 64 | const editor = await atom.workspace.open(passPath); 65 | const messages = await lint(editor); 66 | 67 | expect(messages.length).toBe(0); 68 | }); 69 | }); 70 | 71 | describe('opens ignored.sass and', () => { 72 | it('ignores the file and reports no warnings', async () => { 73 | atom.config.set('linter-sass-lint.configFile', configFile); 74 | const editor = await atom.workspace.open(ignoredPath); 75 | const messages = await lint(editor); 76 | 77 | expect(messages.length).toBe(0); 78 | }); 79 | }); 80 | 81 | describe('opens failure.sass and sets pacakage to not lint if no config file present', () => { 82 | it("doesn't lint the file as there's no config file present", async () => { 83 | atom.config.set('linter-sass-lint.noConfigDisable', true); 84 | atom.config.set('linter-sass-lint.configFile', ''); 85 | const editor = await atom.workspace.open(failurePath); 86 | const result = await lint(editor); 87 | 88 | expect(result).toBe(null); 89 | }); 90 | }); 91 | }); 92 | -------------------------------------------------------------------------------- /spec/linter-sass-lint-scss-spec.js: -------------------------------------------------------------------------------- 1 | 'use babel'; 2 | 3 | import { 4 | // eslint-disable-next-line no-unused-vars 5 | it, fit, wait, beforeEach, afterEach, 6 | } from 'jasmine-fix'; 7 | import { join } from 'path'; 8 | 9 | const { lint } = require('../lib/main').provideLinter(); 10 | 11 | const failurePath = join(__dirname, 'fixtures', 'files', 'failure.scss'); 12 | const ignoredPath = join(__dirname, 'fixtures', 'files', 'ignored.scss'); 13 | const passPath = join(__dirname, 'fixtures', 'files', 'pass.scss'); 14 | const configFile = join(__dirname, 'fixtures', 'config', '.sass-lint.yml'); 15 | 16 | describe('The sass-lint provider for Linter - scss', () => { 17 | beforeEach(async () => { 18 | atom.workspace.destroyActivePaneItem(); 19 | await atom.packages.activatePackage('linter-sass-lint'); 20 | await atom.packages.activatePackage('language-sass'); 21 | }); 22 | 23 | describe('checks failure.scss and', () => { 24 | let messages = null; 25 | 26 | beforeEach(async () => { 27 | atom.config.set('linter-sass-lint.configFile', configFile); 28 | const editor = await atom.workspace.open(failurePath); 29 | messages = await lint(editor); 30 | }); 31 | 32 | it('finds at least one message', () => { 33 | expect(messages.length).toBeGreaterThan(0); 34 | }); 35 | 36 | it('verifies the first message', () => { 37 | const slDocUrl = 'https://github.com/sasstools/sass-lint/tree/master/docs/rules/no-ids.md'; 38 | const warnId = 'ID selectors not allowed (no-ids)'; 39 | 40 | expect(messages[0].severity).toBe('error'); 41 | expect(messages[0].description).not.toBeDefined(); 42 | expect(messages[0].url).toBe(slDocUrl); 43 | expect(messages[0].excerpt).toBe(warnId); 44 | expect(messages[0].location.file).toBe(failurePath); 45 | expect(messages[0].location.position).toEqual([[0, 0], [0, 5]]); 46 | }); 47 | 48 | it('verifies the second message', () => { 49 | const slDocUrl = 'https://github.com/sasstools/sass-lint/tree/master/docs/rules/no-color-literals.md'; 50 | const warnId = 'Color literals such as \'red\' should only be used in variable declarations (no-color-literals)'; 51 | 52 | expect(messages[1].severity).toBe('warning'); 53 | expect(messages[1].description).not.toBeDefined(); 54 | expect(messages[1].url).toBe(slDocUrl); 55 | expect(messages[1].excerpt).toBe(warnId); 56 | expect(messages[1].location.file).toBe(failurePath); 57 | expect(messages[1].location.position).toEqual([[1, 9], [1, 12]]); 58 | }); 59 | }); 60 | 61 | describe('checks pass.scss and', () => { 62 | it('finds nothing wrong with the valid file', async () => { 63 | atom.config.set('linter-sass-lint.configFile', configFile); 64 | const editor = await atom.workspace.open(passPath); 65 | const messages = await lint(editor); 66 | 67 | expect(messages.length).toBe(0); 68 | }); 69 | }); 70 | 71 | describe('opens ignored.scss and', () => { 72 | it('ignores the file and reports no warnings', async () => { 73 | atom.config.set('linter-sass-lint.configFile', configFile); 74 | const editor = await atom.workspace.open(ignoredPath); 75 | const messages = await lint(editor); 76 | 77 | expect(messages.length).toBe(0); 78 | }); 79 | }); 80 | 81 | describe('opens failure.scss and sets pacakage to not lint if no config file present', () => { 82 | it("doesn't lint the file as there's no config file present", async () => { 83 | atom.config.set('linter-sass-lint.noConfigDisable', true); 84 | atom.config.set('linter-sass-lint.configFile', ''); 85 | const editor = await atom.workspace.open(failurePath); 86 | const result = await lint(editor); 87 | 88 | expect(result).toBe(null); 89 | }); 90 | }); 91 | }); 92 | -------------------------------------------------------------------------------- /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | 3 | defaults: &defaults 4 | working_directory: /tmp/project 5 | docker: 6 | - image: arcanemagus/atom-docker-ci:stable 7 | steps: 8 | # Restore project state 9 | - attach_workspace: 10 | at: /tmp 11 | - run: 12 | name: Install Node.js 10 13 | command: | 14 | sudo apt-get update && \ 15 | sudo apt-get install --assume-yes --quiet --no-install-suggests \ 16 | --no-install-recommends gnupg && \ 17 | curl -sL https://deb.nodesource.com/setup_10.x | sudo -E bash - && \ 18 | sudo apt-get update && \ 19 | sudo apt-get install --assume-yes --quiet --no-install-suggests \ 20 | --no-install-recommends nodejs 21 | - run: 22 | name: Node.js version 23 | command: node --version 24 | - run: 25 | name: Install sass-lint globally 26 | command: | 27 | sudo npm install --global sass-lint && \ 28 | sudo rm -Rf ~/.npm 29 | # Removes the .npm folder as the cache is partially polluted as root 30 | - run: 31 | name: Sass Lint version 32 | command: sass-lint --version 33 | - run: 34 | name: Create VFB for Atom to run in 35 | command: /usr/local/bin/xvfb_start 36 | - run: 37 | name: Atom version 38 | command: ${ATOM_SCRIPT_PATH} --version 39 | - run: 40 | name: APM version 41 | command: ${APM_SCRIPT_PATH} --version 42 | - run: 43 | name: Package APM package dependencies 44 | command: | 45 | if [ -n "${APM_TEST_PACKAGES}" ]; then 46 | for pack in ${APM_TEST_PACKAGES}; do 47 | ${APM_SCRIPT_PATH} install "${pack}" 48 | done 49 | fi; 50 | - run: 51 | name: Package dependencies 52 | command: ${APM_SCRIPT_PATH} install 53 | - run: 54 | name: Cleaning package 55 | command: ${APM_SCRIPT_PATH} clean 56 | - run: 57 | name: Package specs 58 | command: ${ATOM_SCRIPT_PATH} --test spec 59 | # Cache node_modules 60 | - save_cache: 61 | paths: 62 | - node_modules 63 | key: v2-dependencies-{{ .Branch }}-{{ checksum "package.json" }}-{{ checksum "package-lock.json"}} 64 | 65 | jobs: 66 | checkout_code: 67 | <<: *defaults 68 | docker: 69 | - image: circleci/node:latest 70 | steps: 71 | - checkout 72 | # Restore node_modules from the last build 73 | - restore_cache: 74 | keys: 75 | # Get latest cache for this package.json and pacakge-lock.json 76 | - v2-dependencies-{{ .Branch }}-{{ checksum "package.json" }}-{{ checksum "package-lock.json"}} 77 | # Fallback to the current package.json 78 | - v2-dependencies-{{ .Branch }}-{{ checksum "package.json" }}- 79 | # Fallback to the last build for this branch 80 | - v2-dependencies-{{ .Branch }}- 81 | # Fallback to the last available master branch cache 82 | - v2-dependencies-master- 83 | # Don't go further down to prevent dependency issues from other branches 84 | # Save project state for next steps 85 | - persist_to_workspace: 86 | root: /tmp 87 | paths: 88 | - project 89 | lint: 90 | <<: *defaults 91 | docker: 92 | - image: circleci/node:latest 93 | steps: 94 | # Restore project state 95 | - attach_workspace: 96 | at: /tmp 97 | - run: 98 | name: Node.js Version 99 | command: node --version 100 | - run: 101 | name: NPM Version 102 | command: npm --version 103 | - run: 104 | name: Install any remaining dependencies 105 | command: npm install 106 | - run: 107 | name: Lint code 108 | command: npm run lint 109 | stable: 110 | <<: *defaults 111 | beta: 112 | <<: *defaults 113 | docker: 114 | - image: arcanemagus/atom-docker-ci:beta 115 | 116 | workflows: 117 | version: 2 118 | test_package: 119 | jobs: 120 | - checkout_code 121 | - lint: 122 | requires: 123 | - checkout_code 124 | - stable: 125 | requires: 126 | - lint 127 | - beta: 128 | requires: 129 | - lint 130 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # linter-sass-lint 2 | 3 | [![Build Status](https://travis-ci.org/AtomLinter/linter-sass-lint.svg)](https://travis-ci.org/AtomLinter/linter-sass-lint) 4 | [![Circle CI](https://circleci.com/gh/AtomLinter/linter-sass-lint/tree/master.svg?style=shield)](https://circleci.com/gh/AtomLinter/linter-sass-lint/tree/master) 5 | [![Build status](https://ci.appveyor.com/api/projects/status/fd4oj1kb84uv5a54/branch/master?svg=true)](https://ci.appveyor.com/project/DanPurdy/linter-sass-lint/branch/master) 6 | 7 | [![apm](https://img.shields.io/apm/l/linter-sass-lint.svg)](https://atom.io/packages/linter-sass-lint) 8 | [![apm](https://img.shields.io/apm/dm/linter-sass-lint.svg)](https://atom.io/packages/linter-sass-lint) 9 | [![apm](https://img.shields.io/apm/v/linter-sass-lint.svg)](https://atom.io/packages/linter-sass-lint) 10 | 11 | This linter plugin for [Linter](https://github.com/AtomLinter/Linter) provides an interface to [sass-lint](https://github.com/sasstools/sass-lint). It will be used with files that have the “Sass” or “SCSS” syntax. 12 | 13 | [sass-lint](https://github.com/sasstools/sass-lint) is a node only sass linter and isn't related to [scss-lint](https://github.com/brigade/scss-lint). 14 | 15 | ### Installation 16 | 17 | You'll need to have [Linter](https://atom.io/packages/linter) installed to use this plugin 18 | 19 | **The current latest version of [sass-lint](https://github.com/sasstools/sass-lint) comes bundled with this plugin but if you'd like to install it manually you can follow the instructions [here](https://github.com/sasstools/sass-lint).** 20 | 21 | #### Plugin installation 22 | 23 | ``` 24 | apm install linter-sass-lint 25 | ``` 26 | 27 | #### .sass-lint.yml 28 | 29 | A `.sass-lint.yml` config file is required for this linter. You can find an example of one [here](https://github.com/sasstools/sass-lint/blob/master/lib/config/sass-lint.yml) and documentation on how to configure this and each of the rules [here](https://github.com/sasstools/sass-lint/tree/master/docs). 30 | 31 | By default this plugin will search up the directory tree for this file, you can also specify a path to this config file in the plugin settings or in `~/.atom/config.cson` file. Usually you would place this config file in your projects root and keep it under version control too. 32 | 33 | You can use the `noConfigDisable` option to prevent any attempts at linting (and the missing config error messages you will encounter) if a valid config is not found. 34 | 35 | By default a config file found in the root of your currently open project will take preference over a config file specified with the `configFile` option. 36 | 37 | ### Settings 38 | 39 | There are three options you can configure either within the plugin or by editing your `~/.atom/config.cson` file. 40 | 41 | * `noConfigDisable` - Enable to prevent any linting if a valid config file (`.sass-lint.yml`) is not found in the project root. 42 | 43 | * `configFile` - this is path to a `.sass-lint.yml` config file, this should only be used if you'd like to specify a global config file rather than rely on a project config file in the root of your project. 44 | 45 | * `globalNodePath` This is where you can set your global node installation path. Run `npm get prefix` and paste the result here. This will be where `linter-sass-lint` will then search for the globally installed version of `sass-lint` if you choose to enable this with `globalSassLint`. 46 | 47 | * `globalSassLint` This allows you to specify that you want to use your globally installed version of `sass-lint` (`npm install -g sass-lint`) instead of the version bundled with `linter-sass-lint`. 48 | 49 | * `resolvePathsRelativeToConfig` This option allows you to choose to resolve file paths relative to your config file rather than relative to the root of your currently open project. 50 | 51 | ### Extra File Extensions 52 | 53 | This plugin will attempt to lint a file with framework specific file extensions on top of the usual `.scss` and `.sass` extensions such as with shopify's `.scss.liquid` extension as long as you still include `.scss` or `.sass` somewhere in the file, you must also ensure that the Atom grammar scope for that file is set to either SCSS or Sass depending on which it corresponds to. 54 | 55 | This does not mean that sass-lint will be able to definitely parse any sort of non standard SCSS or Sass code and if you use any platform specific code in the file it will almost definitely produce a parse error. Sass-lint will not be moving to support any use of non standard language outside of the Sass spec. 56 | 57 | 58 | ### Contributing 59 | 60 | Contributions, suggestions and fixes are more than welcome. 61 | 62 | Please read the [Contribution Guidlines](CONTRIBUTING.md) 63 | 64 | A general sense of the guidelines can be found below. 65 | 66 | 1. Indentation is 2 spaces. 67 | 1. All code should pass the coffeelinter linter, the config of which is included in this repository (`npm-test`). 68 | 1. the .editorconfig file should be used to ensure a consistent style [info here](http://editorconfig.org/) 69 | 70 | General contribution guidelines apply 71 | 72 | 1. Fork the plugin repository 73 | 1. Create a feature/hotfix branch off of master 74 | 1. Lint your code `npm-test` 75 | 1. Commit and push the branch 76 | 1. Make a pull request 77 | 78 | If you're unsure on whether your contribution will be required then please file an issue first and we can discuss it. 79 | 80 | If you find any problems with the `sass-lint` itself with regards to bugs in rules then please visit the [sass-lint Github Page](https://github.com/sasstools/sass-lint) please note that `sass-lint` is young and still under heavy development. 81 | -------------------------------------------------------------------------------- /lib/helpers.js: -------------------------------------------------------------------------------- 1 | 'use babel'; 2 | 3 | import path from 'path'; 4 | import fs from 'fs'; 5 | import { find } from 'atom-linter'; 6 | // NOTE: This rule appears to currently be broken 7 | // eslint-disable-next-line import/named 8 | import { SASSLINT_DOC_URL, VALID_SYNTAXES } from './constants'; 9 | 10 | const severityMap = new Map([[1, 'warning'], [2, 'error']]); 11 | 12 | export default { 13 | /** 14 | * Function to construct the rule URI from the rule ID provided 15 | * @param {string} ruleId - The rule name / id 16 | * @return {string} The rule URL 17 | */ 18 | getRuleURI(ruleId) { 19 | return `${SASSLINT_DOC_URL}/${ruleId}.md`; 20 | }, 21 | 22 | /** 23 | * Function to check a file base / extension for valid extensions to use with 24 | * sass-lint 25 | * @param {string} syntax - The syntax to check 26 | * @return {boolean} Whether or not the syntax is valid for sass-lint 27 | */ 28 | isValidSyntax(syntax) { 29 | return VALID_SYNTAXES.includes(syntax); 30 | }, 31 | 32 | /** 33 | * Function to check a file base / extension for valid extensions to use with 34 | * sass-lint 35 | * @param {string} filePath - The filepath to check 36 | * @return {string} The syntax we wish to pass to sass-lint 37 | */ 38 | getFileSyntax(filePath) { 39 | const existingSyntax = path.extname(filePath).slice(1); 40 | if (!this.isValidSyntax(existingSyntax)) { 41 | const base = path.parse(filePath).base.split('.'); 42 | const syntax = base.reduce((acc, item) => { 43 | if (this.isValidSyntax(item)) { 44 | return acc.concat(item); 45 | } 46 | return acc; 47 | }, []); 48 | if (syntax.length) { 49 | return syntax[0]; 50 | } 51 | } 52 | 53 | return existingSyntax; 54 | }, 55 | 56 | /** 57 | * Attempts to resolve the root directory/project directory of the currently 58 | * open file/editor instance 59 | * @param {string} filePath - The currently active editor file path 60 | * @return {Object|null} - The project directory instance or null if no 61 | * project root is found 62 | */ 63 | getRootDir(filePath) { 64 | return atom.project.relativizePath(filePath)[0]; 65 | }, 66 | 67 | /** 68 | * Checks to see if a config file exists in the project's root directory if a 69 | * root directory exists 70 | * @param {string|null} dir - The current project root or null if a project 71 | * doesn't exist 72 | * @param {string} configExt - The Sass-lint config extension 73 | * @return {string|null} - The path to the config file if located in the 74 | * project root, null if it doesn't exist 75 | */ 76 | getRootDirConfig(dir, configExt) { 77 | const rootDir = dir; 78 | 79 | if (rootDir) { 80 | const confLoc = path.join(rootDir, configExt); 81 | try { 82 | fs.accessSync(confLoc, fs.R_OK); 83 | return confLoc; 84 | } catch (error) { 85 | return null; 86 | } 87 | } 88 | 89 | return rootDir; 90 | }, 91 | 92 | /** 93 | * Looks for and returns the path to a projects config file or null if it 94 | * can't be found or doesn't exist 95 | * @param {string} filePath - The currently active editor file path 96 | * @param {string} configExt - The Sass-lint config extension 97 | * @param {boolean} noRootConfDisable - The user specified option to disable 98 | * linter-sass-lint if no config is found 99 | * in the root of the project 100 | * @return {string|null} The path to the config file or null if not found 101 | */ 102 | getConfig(filePath, configExt, noRootConfDisable) { 103 | const rootDir = this.getRootDir(filePath); 104 | const rootDirConfig = this.getRootDirConfig(rootDir, configExt); 105 | 106 | if (noRootConfDisable === true && rootDirConfig === null) { 107 | return null; 108 | } 109 | 110 | return find(filePath, configExt); 111 | }, 112 | 113 | /** 114 | * Return the string severity for a numerical one, defalting to info 115 | * @param {number} severity severity from the sass-lint linter 116 | * @return {string} severity for the Linter API 117 | */ 118 | getSeverity: (severity) => (severityMap.has(severity) 119 | ? severityMap.get(severity) 120 | : 'info' 121 | ), 122 | 123 | warningConfigFile: () => { 124 | atom.notifications.addWarning(`\ 125 | **Config File Error** 126 | 127 | The config file you specified doesn't seem to be a .yml file.\n 128 | Please see the sass-lint [documentation](https://github.com/sasstools/sass-lint/tree/master/docs) on how to create a config file.\ 129 | `); 130 | }, 131 | 132 | errorGettingPath: () => { 133 | atom.notifications.addError(`\ 134 | **Error getting $PATH - linter-sass-lint** 135 | 136 | You've enabled using global sass-lint without specifying a prefix so we tried to. 137 | Unfortunately we were unable to execute \`npm get prefix\` for you.. 138 | Please make sure Atom is getting $PATH correctly or set it directly in the \`linter-sass-lint\` settings.\ 139 | `, { dismissable: true }); 140 | }, 141 | 142 | warningSassLintMissing: () => { 143 | atom.notifications.addWarning(`\ 144 | **Sass-lint package missing** 145 | 146 | The sass-lint package cannot be found, please check sass-lint is installed globally. 147 | You can always use the sass-lint pacakage included with linter-sass-lint by disabling the 148 | \`Use global sass-lint installation\` option\ 149 | `, { dismissable: true }); 150 | }, 151 | }; 152 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # 1.9.0 2 | - Update to linter v2 API 3 | 4 | # 1.8.3 5 | - Update sass-lint version to 1.12.1 6 | 7 | # 1.8.2 8 | - Update sass-lint version to 1.12.0 9 | - Update dependencies 10 | 11 | # 1.8.1 12 | - apm publish issue - no such version 13 | 14 | # 1.8.0 15 | - Reworked the way that a config file is resolved to fix issues where configs in project roots were being ignored. 16 | - Updated a few dependencies 17 | 18 | # 1.7.6 19 | - Update sass-lint version to 1.11.1 20 | 21 | ## 1.7.5 22 | - Update sass-lint version to 1.11.0 23 | 24 | ## 1.7.4 25 | - Update sass-lint version to 1.10.2 26 | 27 | ## 1.7.3 28 | - Update sass-lint version to 1.10.1 29 | 30 | ## 1.7.2 31 | - Update sass-lint version to 1.10.0 - adds support for disabling linters via comments 32 | - Updated other third party packages 33 | 34 | ## 1.7.1 35 | - Update sass-lint version to 1.9.1 to force fix AST issues included with sass-lint 36 | - Updated other third party packages 37 | 38 | ## 1.7.0 39 | - Package start up time improvements thanks to [@ypresto](https://github.com/ypresto) 40 | - Update third party packages 41 | - Updated CI configurations thanks [@Arcanemagus](https://github.com/Arcanemagus) 42 | 43 | ## 1.6.1 44 | - Force update to sass-lint 1.8.0 due to the high number of fixes and also AST fixes 45 | 46 | ## 1.6.0 47 | - Adds `resolvePathsRelativeToConfig` to the config options to allow paths to be resolved relative to your config file rather than project root. Thanks to [@DirtyHairy](https://github.com/DirtyHairy) 48 | - The usual third party package updates 49 | 50 | ## 1.5.0 51 | - Allows the inclusion of files with extra extensions such as 'file.scss.liquid' 52 | - Updated to Atom linter ^5.0.1 53 | - Includes eslint updates for development 54 | 55 | ## 1.4.3 56 | - Force latest version of sass-lint 1.7.0 57 | - Update a few dependencies 58 | 59 | ## 1.4.2 60 | - Include latest version of sass-lint 61 | - Fix dependency issue with eslint-config-airbnb-base 62 | 63 | ## 1.4.1 64 | - Update included version of sass-lint to 1.6.0 :tada: 65 | 66 | ## 1.4.0 67 | - Updated eslint and globule dependencies 68 | - Added links to the sass-lint rule documentation on all rule id badges/lint messages 69 | - Updated tests for new lint message format 70 | 71 | ## 1.3.1 72 | - Updated multiple dependencies 73 | - Removed unnecessary test characters 74 | 75 | ## 1.3.0 76 | - Updated to use consistent-env rather than the consistent-path package 77 | - Updated to use the latest eslint (2.5.1) and eslint-config-airbnb (6.2.0) packages 78 | - Added multiple node versions to our travis test file 79 | 80 | ### 1.2.0 81 | - Updated eslint to use v2.4.0 [#54](https://github.com/AtomLinter/linter-sass-lint/pull/54) 82 | - Improved package startup time. [#55](https://github.com/AtomLinter/linter-sass-lint/pull/55) 83 | 84 | Thanks to 85 | - [ypresto](https://github.com/ypresto) 86 | - [Arcanemagus](https://github.com/Arcanemagus) 87 | 88 | ### 1.1.2 89 | - Lock down eslint to 2.2.x due to [eslint #5476](https://github.com/eslint/eslint/issues/5476) 90 | 91 | ### 1.1.1 92 | - Updated eslint to ^2.2.0 93 | - Updated eslint-config-airbnb to ^6.0.1 94 | 95 | ### 1.1.0 96 | - Updated no config error messages to return info type lint warnings instead (popups are annoying) 97 | - Updated a few package dependencies (eslint-config-airbnb, atom-package-deps) 98 | 99 | ### 1.0.5 100 | - Temporarily locked down our dependency on `atom-package-deps` to v3.0.6 101 | 102 | ### 1.0.4 103 | - Temporarily locked down our dependency on consistent-path to v1.0.3 104 | - Updated to the airbnb's eslint config v5 105 | 106 | ### 1.0.3 107 | - Unexpected parse errors and sass-lint issues are just reported as lint errors rather than the annoying red box of doom 108 | - this will be updated in 1.1.0 to improve the way these are reported and handled, for now the errors will be logged to the console. 109 | 110 | ### 1.0.2 111 | - Update eslint-config-airbnb to version 4.0.0 112 | 113 | ### 1.0.1 114 | - Updated the README.md to reflect the latest option changes in 1.0.0 115 | 116 | ### 1.0.0 117 | - Added 'Lint On Fly' support [#11](https://github.com/AtomLinter/linter-sass-lint/issues/11) thanks to [Casey Foster](https://github.com/caseywebdev) 118 | - Removed the `executablePath` option 119 | - Removed the ability to specify a path to a config you must now explicitly define a config file if you use this option 120 | - project configs remain untouched 121 | - this has been deprecated since 0.4.0 122 | - Added a `globalSassLint` option to use your globally installed `sass-lint` package rather than the version included with `linter-sass-lint` [#3](https://github.com/AtomLinter/linter-sass-lint/issues/3) 123 | - Automatically look to your global modules in the event the above option is enabled but you've not set your path prefix. 124 | - Fixed an issue with ignored and included files options of your config being ignored, ignored files will now not be linted. See the `sass-lint` config file [documentation](https://github.com/sasstools/sass-lint/tree/master/docs) for more information on how to use this. 125 | - Fixed a few inconsistencies in the package tests 126 | - Added tests for the new options included in 1.0.0 127 | 128 | ### 0.6.3 129 | - Update to latest airbnb eslint dependency 130 | - Slight refactor of tests to meet new standards 131 | 132 | ### 0.6.2 133 | - Renamed incorrectly named syntax tests 134 | 135 | ### 0.6.1 136 | - fix protected branch issue with apm publish 137 | 138 | ### 0.6.0 139 | - Tests! Specs for sass and scss. 140 | - Added Travis, CircleCi & Appveyor builds 141 | - Added and modified a few dotfiles for project consistency 142 | 143 | ### 0.5.1 144 | - Added contribution guidelines 145 | - Added .editorconfig for consistent contributions [http://editorconfig.org/](http://editorconfig.org/) 146 | - Updated executablePath option description 147 | - Updated project description 148 | - Added missing documentation for noConfigDisable option 149 | 150 | ### 0.5.0 151 | - Improved error messages 152 | - Added option to disable linting if no config file is found 153 | - Config files in the project root are now preferred over the config path option 154 | - Updated dependencies and added development lint step 155 | - Improved formatting of lint results 156 | 157 | ### 0.4.4 158 | - Moved over to the AtomLinter organisation 159 | 160 | ### 0.4.3 161 | - oops, wrong branch 162 | 163 | ### 0.4.2 164 | - Small update to bump minimum sass-lint version to v1.4.0 165 | 166 | ### 0.4.1 167 | - Fixed a possible error that would cause sass-lint to break if you tried to lint in an ignored file 168 | 169 | ### 0.4.0 170 | - Updated sass-lint dependency to 1.3.1 171 | - Modify the config path option to accept files as well as paths. 172 | - Update config path description 173 | - Add deprecation warning messages 174 | 175 | **Deprecations** 176 | 177 | - The config path will soon only accept paths with filenames specified for the user config. A deprecation warning message is displayed to urge users to switch 178 | 179 | ### 0.3.0 180 | - Updated README.md with correct branches and updated spelling 181 | - Added nicer block parsing error messages [#6](https://github.com/DanPurdy/linter-sass-lint/pull/6) (thanks to [josa42](https://github.com/josa42)) 182 | - Updated missing config warning message to provide more useful information [#5](https://github.com/DanPurdy/linter-sass-lint/pull/5) (thanks to [josa42](https://github.com/josa42)) 183 | 184 | ### 0.2.1 185 | - Added name to linter warnings [#4](https://github.com/DanPurdy/linter-sass-lint/pull/4) (thanks to [steelbrain](https://github.com/steelbrain)) 186 | 187 | ### 0.2.0 188 | 189 | - Bump `sass-lint` version 190 | - Update package description 191 | - Update output format to show rule names 192 | 193 | ### 0.1.5 194 | 195 | - Bump `sass-lint` version 196 | - Introduce changelog 197 | 198 | ### 0.1.4 199 | 200 | - Update package keywords 201 | 202 | ### 0.1.3 203 | 204 | - Update to readme instructions 205 | 206 | ### 0.1.2 207 | 208 | - Corrected documentation 209 | 210 | ### 0.1.1 211 | 212 | - Added MIT license 213 | 214 | ### 0.1.0 215 | 216 | - Initial release 217 | -------------------------------------------------------------------------------- /lib/main.js: -------------------------------------------------------------------------------- 1 | 'use babel'; 2 | 3 | import { CompositeDisposable } from 'atom'; 4 | 5 | // Dependencies 6 | let path; 7 | let globule; 8 | let spawnSync; 9 | let consistentEnv; 10 | let generateRange; 11 | let helpers; 12 | 13 | function loadDeps() { 14 | if (!path) { 15 | path = require('path'); 16 | } 17 | if (!globule) { 18 | globule = require('globule'); 19 | } 20 | if (!consistentEnv) { 21 | consistentEnv = require('consistent-env'); 22 | } 23 | if (!spawnSync) { 24 | ({ spawnSync } = require('child_process')); 25 | } 26 | if (!generateRange) { 27 | ({ generateRange } = require('atom-linter')); 28 | } 29 | if (!helpers) { 30 | helpers = require('./helpers'); 31 | } 32 | } 33 | 34 | const idleCallbacks = new Set(); 35 | const makeIdleCallback = (work) => { 36 | let callbackId; 37 | const callBack = () => { 38 | idleCallbacks.delete(callbackId); 39 | work(); 40 | }; 41 | callbackId = window.requestIdleCallback(callBack); 42 | idleCallbacks.add(callbackId); 43 | }; 44 | 45 | let prefixPath = null; 46 | 47 | export default { 48 | activate() { 49 | this.subs = new CompositeDisposable(); 50 | this.subs.add( 51 | atom.config.observe('linter-sass-lint.noConfigDisable', 52 | (value) => { this.noConfigDisable = value; }), 53 | atom.config.observe('linter-sass-lint.configFile', 54 | (value) => { this.configFile = value; }), 55 | atom.config.observe('linter-sass-lint.globalSassLint', 56 | (value) => { this.globalSassLint = value; }), 57 | atom.config.observe('linter-sass-lint.globalNodePath', 58 | (value) => { this.globalPath = value; }), 59 | atom.config.observe('linter-sass-lint.resolvePathsRelativeToConfig', 60 | (value) => { this.resolvePathsRelativeToConfig = value; }), 61 | ); 62 | 63 | if (!atom.inSpecMode()) { 64 | const loadLinterSassLintDependencies = () => { loadDeps(); }; 65 | const linterSassLintInstallPeerPackages = () => { 66 | require('atom-package-deps').install('linter-sass-lint'); 67 | }; 68 | makeIdleCallback(loadLinterSassLintDependencies); 69 | makeIdleCallback(linterSassLintInstallPeerPackages); 70 | } 71 | }, 72 | 73 | deactivate() { 74 | idleCallbacks.forEach((callbackID) => window.cancelIdleCallback(callbackID)); 75 | idleCallbacks.clear(); 76 | return this.subs.dispose(); 77 | }, 78 | 79 | // return a relative path for a file within our project 80 | // we use this to match it to our include/exclude glob string within sass-lint's 81 | // user specified config 82 | getFilePath(absolutePath, configFilePath) { 83 | if (this.resolvePathsRelativeToConfig) { 84 | return path.relative(path.dirname(configFilePath), absolutePath); 85 | } 86 | return atom.project.relativizePath(absolutePath)[1]; 87 | }, 88 | 89 | // Determines whether to use the sass-lint package included with linter-sass-lint 90 | // or the users globally installed sass-lint version 91 | findExecutable() { 92 | // FIXME: use resolve here 93 | if (!this.globalSassLint) { 94 | // eslint-disable-next-line import/no-dynamic-require 95 | return require(path.join(__dirname, '..', 'node_modules', 'sass-lint')); 96 | } 97 | if ((this.globalPath === '') && (prefixPath === null)) { 98 | const npmCommand = process.platform === 'win32' ? 'npm.cmd' : 'npm'; 99 | const env = { ...consistentEnv() }; 100 | try { 101 | prefixPath = spawnSync(npmCommand, [ 102 | 'get', 103 | 'prefix', 104 | ], { env }).output[1].toString().trim(); 105 | } catch (e) { 106 | throw new Error('prefix'); 107 | } 108 | } 109 | if (process.platform === 'win32') { 110 | // eslint-disable-next-line import/no-dynamic-require 111 | return require(path.join(this.globalPath || prefixPath, 'node_modules', 'sass-lint')); 112 | } 113 | // eslint-disable-next-line import/no-dynamic-require 114 | return require(path.join(this.globalPath || prefixPath, 'lib', 'node_modules', 'sass-lint')); 115 | }, 116 | 117 | provideLinter() { 118 | return { 119 | name: 'sass-lint', 120 | grammarScopes: ['source.css.scss', 'source.scss', 'source.css.sass', 'source.sass'], 121 | scope: 'file', 122 | lintsOnChange: true, 123 | lint: (editor) => { 124 | // Force the dependencies to load if they haven't already 125 | loadDeps(); 126 | 127 | const filePath = editor.getPath(); 128 | const projectConfig = helpers.getConfig( 129 | filePath, '.sass-lint.yml', this.noConfigDisable, 130 | ); 131 | const globalConfig = this.configFile === '' ? null : this.configFile; 132 | const config = projectConfig !== null ? projectConfig : globalConfig; 133 | 134 | let linter; 135 | try { 136 | linter = this.findExecutable(); 137 | } catch (error) { 138 | if (error.message === 'prefix') { 139 | helpers.errorGettingPath(); 140 | return null; 141 | } 142 | 143 | helpers.warningSassLintMissing(); 144 | return null; 145 | } 146 | 147 | if ((config !== null) && (path.extname(config) !== '.yml')) { 148 | helpers.warningConfigFile(); 149 | } 150 | 151 | if ((config === null) && (this.noConfigDisable === false)) { 152 | return [{ 153 | severity: 'info', 154 | excerpt: 'No .sass-lint.yml config file detected or specified. ' 155 | + 'Please check your settings', 156 | location: { 157 | file: filePath, 158 | position: generateRange(editor, 0), 159 | }, 160 | }]; 161 | } 162 | 163 | if (config === null && this.noConfigDisable === true) { 164 | return null; 165 | } 166 | 167 | let result; 168 | try { 169 | const compiledConfig = linter.getConfig({}, config); 170 | const relativePath = this.getFilePath(filePath, config); 171 | 172 | if ( 173 | globule.isMatch(compiledConfig.files.include, relativePath) 174 | && !globule.isMatch(compiledConfig.files.ignore, relativePath) 175 | ) { 176 | result = linter.lintText({ 177 | text: editor.getText(), 178 | format: helpers.getFileSyntax(filePath), 179 | filename: filePath, 180 | }, {}, config); 181 | } 182 | } catch (error) { 183 | const match = error.message.match(/Parsing error at [^:]+: (.*) starting from line #(\d+)/); 184 | if (match) { 185 | const lineIdx = (Number.parseInt(match[2], 10) || 1) - 1; 186 | 187 | return [{ 188 | severity: 'error', 189 | excerpt: `Parsing error: ${match[1]}.`, 190 | location: { 191 | file: filePath, 192 | position: generateRange(editor, lineIdx), 193 | }, 194 | }]; 195 | } 196 | 197 | // Leaving this here to allow people to report the errors 198 | // eslint-disable-next-line no-console 199 | console.log('linter-sass-lint', error); 200 | return [{ 201 | severity: 'error', 202 | excerpt: 'Unexpected parse error in file', 203 | location: { 204 | file: filePath, 205 | position: generateRange(editor, 0), 206 | }, 207 | }]; 208 | } 209 | 210 | if (result) { 211 | return result.messages.map((msg) => { 212 | const line = msg.line ? msg.line - 1 : 0; 213 | const col = msg.column ? msg.column - 1 : 0; 214 | const excerpt = msg.message 215 | ? `${msg.message} (${msg.ruleId})` 216 | : 'Unknown Error'; 217 | 218 | result = { 219 | severity: helpers.getSeverity(msg.severity), 220 | excerpt, 221 | url: helpers.getRuleURI(msg.ruleId), 222 | location: { 223 | file: filePath, 224 | position: generateRange(editor, line, col), 225 | }, 226 | }; 227 | 228 | return result; 229 | }); 230 | } 231 | 232 | return []; 233 | }, 234 | }; 235 | }, 236 | }; 237 | --------------------------------------------------------------------------------