├── .gitignore ├── .travis.yml ├── CHANGELOG.md ├── LICENSE ├── README.md ├── lib ├── init.coffee ├── linter-rust.coffee └── mode.coffee ├── package-lock.json ├── package.json └── spec └── parse-spec.coffee /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | ### Project specific config ### 2 | os: linux 3 | 4 | jobs: 5 | include: 6 | - stage: test 7 | language: rust 8 | rust: stable 9 | env: ATOM_CHANNEL=stable 10 | script: skip 11 | - stage: test 12 | language: rust 13 | rust: beta 14 | env: ATOM_CHANNEL=beta 15 | script: skip 16 | 17 | ### Generic setup follows ### 18 | install: 'curl -s https://raw.githubusercontent.com/atom/ci/master/build-package.sh | sh' 19 | 20 | notifications: 21 | email: 22 | on_success: never 23 | on_failure: change 24 | 25 | branches: 26 | only: 27 | - master 28 | - "/^greenkeeper/.*$/" 29 | 30 | git: 31 | depth: 10 32 | 33 | dist: trusty 34 | sudo: false 35 | 36 | addons: 37 | apt: 38 | packages: 39 | - build-essential 40 | - git 41 | - libgnome-keyring-dev 42 | - fakeroot 43 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # 1.0.1 2 | 3 | * Reference correct message property for file path (#146) 4 | 5 | # 1.0.0 6 | 7 | * Update to Linter v2 API (#143) 8 | * Remove requirement on `language-rust` (#144) 9 | 10 | # 0.9.0 11 | * Add option for `cargo check --tests` (@SnirkImmington) 12 | 13 | ## [0.8.5](https://github.com/AtomLinter/linter-rust/compare/v0.8.4...v0.8.5) (2018-04-22) 14 | 15 | ### Bug Fixes 16 | 17 | * **package:** update xregexp to version 4.1.1 ([8d401d6](https://github.com/AtomLinter/linter-rust/commit/8d401d6)), closes [#124](https://github.com/AtomLinter/linter-rust/issues/124) 18 | 19 | # 0.8.4 20 | * Fix 'check all' and Add 'test all' (@alkorang) 21 | * Description for 'check all' and 'test all' (@alkorang) 22 | 23 | # 0.8.3 24 | * Add --tests flag to `check all` mode (@HyeonuPark) 25 | 26 | # 0.8.2 27 | * Add support for "cargo check --all" (@arthurprs) 28 | 29 | # 0.8.1 30 | Fix clippy `--manifest-path` usage (@alexheretic) 31 | 32 | # 0.8.0 33 | * Add option to disable execution timeout (@Arcanemagus) 34 | * Don't pass null to dirname (@Arcanemagus) 35 | 36 | # 0.7.3 37 | * Update `atom-linter` to v9.0.0 (@Arcanemagus) 38 | 39 | # 0.7.2 40 | * Removed `-Zno-trans` option for forward compatibility (@White-Oak) 41 | 42 | # 0.7.1 43 | * Added beta and stable versions to proper JSON support mode (@White-Oak) 44 | 45 | # 0.7.0 46 | * Added a proper support for cargo --message-format json (@White-Oak) 47 | * Fix span for macro invocations (@utkarshkukreti) 48 | 49 | # 0.6.0 50 | * Use asynchronous execs instead of synchronous (@White-Oak) 51 | 52 | # 0.5.7 53 | * Assume non-nightly versions >= 1.12.0, fix #78 (@jviide) 54 | 55 | # 0.5.6 56 | * Fixed the way json errors are requested from cargo & rustc, fix #69 (@White-Oak) 57 | 58 | # 0.5.5 59 | * Update atom-package-deps to version 4.3.0 60 | * Fix #70 (@Maplicant) 61 | 62 | # 0.5.4 63 | * Fix indexOf check (@benstreb) 64 | 65 | # 0.5.2 66 | * Removed default values for features specified, fix #65 (@White-Oak) 67 | 68 | # 0.5.1 69 | * Ability to specify features to lint (@White-Oak) 70 | 71 | # 0.5.0 72 | * Added support for JSON error output of rustc (@White-Oak) 73 | 74 | # 0.4.6 75 | * Fixed undesirable modification of global objects (@lhecker) 76 | * Trim whitespace from rustc and cargo paths (@chriskrycho) 77 | 78 | # 0.4.5 79 | * Added fallback to cargo for clippy if multirust isn't available (@White-Oak) 80 | * Added option to disable certain lintings in editor (@White-Oak) 81 | 82 | # 0.4.1 83 | * Update xregexp to version 3.1.0 84 | * Allow using multirust for clippy (@White-Oak) 85 | 86 | # 0.4.0 87 | * Add choice to use clippy for linting. Tanks to @White-Oak 88 | 89 | # 0.3.0 90 | * Minor refactor + option for cargo-check (@colin-kiegel) 91 | 92 | # 0.2.11 93 | * Add option to use "cargo rustc" instead of "cargo build" 94 | 95 | # 0.2.10 96 | * Add name for the linter (@LegNeato) 97 | 98 | # 0.2.9 99 | * Fix #36 (@andrewrynhard) 100 | 101 | # 0.2.8 102 | * Include rustc's help messages in lints (@johnsoft) 103 | 104 | # 0.2.7 105 | * Use "Trace" instead of "Warning" for rustc's informational messages (@johnsoft) 106 | 107 | # 0.2.6 108 | * Add config option for building test code (@dgriffen) 109 | 110 | # 0.2.5 111 | * Changed 'rustc' and 'cargo' commands so that test code gets linted in addition to normal code (@psFried) 112 | 113 | # 0.2.4 114 | * Support multiline lint messages 115 | * Use explicit path to rusts executables 116 | * Move parsing into dedicated method for testability 117 | 118 | 119 | # 0.2.3 120 | * Include columns in lint range (thanks to @b52) 121 | 122 | # 0.2.2 123 | * Remove 'linter-package' from the package.json. Close #23 124 | 125 | # 0.2.1 126 | * Fix and close #20. 127 | 128 | # 0.2.0 129 | * Migrate to linter-plus 130 | * Add Rust home path configuration option 131 | 132 | # 0.1.0 133 | * Remove linting "on the fly" 134 | * Support Cargo. See #5. Thanks @liigo for ideas 135 | 136 | # 0.0.13 137 | Added linting on the fly (experimental) 138 | 139 | # 0.0.12 140 | Fix #11 141 | 142 | # 0.0.11 143 | Fix deprecated activationEvents. Closes #10 144 | 145 | # 0.0.10 146 | Extern crate problem fix. See PR #9 147 | 148 | # 0.0.9 149 | Fix spaces problem in the executable path. Closes #3 150 | 151 | # 0.0.8 152 | Show file name where an error occurred 153 | 154 | # 0.0.7 155 | Fix uncaught error 'ENOENT'. See #3 and AtomLinter/Linter#330 156 | 157 | # 0.0.6 158 | Fix rustc command-line options 159 | 160 | # 0.0.5 161 | Add info support. See #7 162 | 163 | # 0.0.4 164 | Correct regex string. See #6 165 | 166 | # 0.0.3 167 | Check original file, not a tmp copy. See #1 168 | 169 | # 0.0.2 170 | Used JSON Schema for linter config 171 | 172 | # 0.0.1 173 | * Implemented first version of 'linter-rust' 174 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # linter-rust [![Build Status](https://travis-ci.org/AtomLinter/linter-rust.svg?branch=master)](https://travis-ci.org/AtomLinter/linter-rust) 2 | 3 | Linting your Rust-files in [Atom](https://atom.io), using [rustc](http://www.rust-lang.org) and [cargo](https://crates.io). 4 | Files will be checked when you open or save them. 5 | 6 | ## Installation 7 | 8 | * Install [Rust](http://www.rust-lang.org) and/or [Cargo](https://crates.io). 9 | * `$ apm install linter` (if you don't have [AtomLinter/Linter](https://github.com/AtomLinter/Linter) installed). 10 | * `$ apm install linter-rust` 11 | 12 | ## Other available linters 13 | There are other linters available - take a look at the linters [mainpage](http://atomlinter.github.io). 14 | -------------------------------------------------------------------------------- /lib/init.coffee: -------------------------------------------------------------------------------- 1 | {CompositeDisposable} = require 'atom' 2 | 3 | module.exports = 4 | config: 5 | useCargo: 6 | type: 'boolean' 7 | default: true 8 | description: "Use Cargo if it's possible" 9 | rustcPath: 10 | type: 'string' 11 | default: 'rustc' 12 | description: "Path to Rust's compiler `rustc`" 13 | cargoPath: 14 | type: 'string' 15 | default: 'cargo' 16 | description: "Path to Rust's package manager `cargo`" 17 | cargoCommand: 18 | type: 'string' 19 | default: 'test all' 20 | enum: [ 21 | 'build' 22 | 'check' 23 | 'check all' 24 | 'check tests' 25 | 'test' 26 | 'test all' 27 | 'rustc' 28 | 'clippy' 29 | ] 30 | description: """`cargo` command to run.""" 40 | cargoManifestFilename: 41 | type: 'string' 42 | default: 'Cargo.toml' 43 | description: 'Cargo manifest filename' 44 | jobsNumber: 45 | type: 'integer' 46 | default: 2 47 | enum: [1, 2, 4, 6, 8, 10] 48 | description: 'Number of jobs to run Cargo in parallel' 49 | disabledWarnings: 50 | type: 'array' 51 | default: [] 52 | items: 53 | type: 'string' 54 | description: 'Linting warnings to be ignored in editor, separated with commas.' 55 | specifiedFeatures: 56 | type: 'array' 57 | default: [] 58 | items: 59 | type: 'string' 60 | description: 'Additional features to be passed, when linting (for example, `secure, html`)' 61 | rustcBuildTest: 62 | type: 'boolean' 63 | default: false 64 | description: "Lint test code, when using `rustc`" 65 | allowedToCacheVersions: 66 | type: 'boolean' 67 | default: true 68 | description: "Uncheck this if you need to change toolchains during one Atom session. Otherwise toolchains' versions are saved for an entire Atom session to increase performance." 69 | disableExecTimeout: 70 | title: "Disable Execution Timeout" 71 | type: 'boolean' 72 | default: false 73 | description: "By default processes running longer than 10 seconds will be automatically terminated. Enable this option if you are getting messages about process execution timing out." 74 | 75 | activate: -> 76 | require('atom-package-deps').install 'linter-rust' 77 | 78 | provideLinter: -> 79 | LinterRust = require('./linter-rust') 80 | @provider = new LinterRust() 81 | { 82 | name: 'Rust' 83 | grammarScopes: ['source.rust'] 84 | scope: 'project' 85 | lint: @provider.lint 86 | lintsOnChange: false 87 | } 88 | -------------------------------------------------------------------------------- /lib/linter-rust.coffee: -------------------------------------------------------------------------------- 1 | fs = require 'fs' 2 | path = require 'path' 3 | 4 | {CompositeDisposable} = require 'atom' 5 | atom_linter = require 'atom-linter' 6 | semver = require 'semver' 7 | XRegExp = require 'xregexp' 8 | 9 | errorModes = require './mode' 10 | 11 | class LinterRust 12 | patternRustcVersion: XRegExp('rustc (?1.\\d+.\\d+)(?:(?:-(?:(?nightly)|(?beta.*?))|(?:[^\s]+))? \ 13 | \\((?:[^\\s]+) (?\\d{4}-\\d{2}-\\d{2})\\))?') 14 | cargoDependencyDir: "target/debug/deps" 15 | 16 | constructor: -> 17 | @subscriptions = new CompositeDisposable 18 | 19 | @subscriptions.add atom.config.observe 'linter-rust.rustcPath', 20 | (rustcPath) => 21 | rustcPath = do rustcPath.trim if rustcPath 22 | @rustcPath = rustcPath 23 | 24 | @subscriptions.add atom.config.observe 'linter-rust.cargoPath', 25 | (cargoPath) => 26 | @cargoPath = cargoPath 27 | 28 | @subscriptions.add atom.config.observe 'linter-rust.useCargo', 29 | (useCargo) => 30 | @useCargo = useCargo 31 | 32 | @subscriptions.add atom.config.observe 'linter-rust.cargoCommand', 33 | (cargoCommand) => 34 | @cargoCommand = cargoCommand 35 | 36 | @subscriptions.add atom.config.observe 'linter-rust.rustcBuildTest', 37 | (rustcBuildTest) => 38 | @rustcBuildTest = rustcBuildTest 39 | 40 | @subscriptions.add atom.config.observe 'linter-rust.cargoManifestFilename', 41 | (cargoManifestFilename) => 42 | @cargoManifestFilename = cargoManifestFilename 43 | 44 | @subscriptions.add atom.config.observe 'linter-rust.jobsNumber', 45 | (jobsNumber) => 46 | @jobsNumber = jobsNumber 47 | 48 | @subscriptions.add atom.config.observe 'linter-rust.disabledWarnings', 49 | (disabledWarnings) => 50 | @disabledWarnings = disabledWarnings 51 | 52 | @subscriptions.add atom.config.observe 'linter-rust.specifiedFeatures', 53 | (specifiedFeatures) => 54 | @specifiedFeatures = specifiedFeatures 55 | 56 | @subscriptions.add atom.config.observe 'linter-rust.allowedToCacheVersions', 57 | (allowedToCacheVersions) => 58 | @allowedToCacheVersions = allowedToCacheVersions 59 | 60 | @subscriptions.add atom.config.observe 'linter-rust.disableExecTimeout', 61 | (value) => 62 | @disableExecTimeout = value 63 | 64 | destroy: -> 65 | do @subscriptions.dispose 66 | 67 | lint: (textEditor) => 68 | @initCmd(textEditor.getPath()).then (result) => 69 | [cmd_res, errorMode] = result 70 | [file, cmd] = cmd_res 71 | env = JSON.parse JSON.stringify process.env 72 | curDir = if file? then path.dirname file else __dirname 73 | cwd = curDir 74 | command = cmd[0] 75 | cmdPath = if cmd[0]? then path.dirname cmd[0] else __dirname 76 | args = cmd.slice 1 77 | env.PATH = cmdPath + path.delimiter + env.PATH 78 | 79 | # we set flags only for intermediate json support 80 | if errorMode == errorModes.FLAGS_JSON_CARGO 81 | if !env.RUSTFLAGS? or !(env.RUSTFLAGS.indexOf('--error-format=json') >= 0) 82 | additional = if env.RUSTFLAGS? then ' ' + env.RUSTFLAGS else '' 83 | env.RUSTFLAGS = '--error-format=json' + additional 84 | 85 | execOpts = 86 | env: env 87 | cwd: cwd 88 | stream: 'both' 89 | execOpts.timeout = Infinity if @disableExecTimeout 90 | 91 | atom_linter.exec(command, args, execOpts) 92 | .then (result) => 93 | {stdout, stderr, exitCode} = result 94 | # first, check if an output says specified features are invalid 95 | if stderr.indexOf('does not have these features') >= 0 96 | atom.notifications.addError "Invalid specified features", 97 | detail: "#{stderr}" 98 | dismissable: true 99 | [] 100 | # then, if exit code looks okay, process an output 101 | else if exitCode is 101 or exitCode is 0 102 | # in dev mode show message boxes with output 103 | showDevModeWarning = (stream, message) -> 104 | atom.notifications.addWarning "Output from #{stream} while linting", 105 | detail: "#{message}" 106 | description: "This is shown because Atom is running in dev-mode and probably not an actual error" 107 | dismissable: true 108 | if do atom.inDevMode 109 | showDevModeWarning('stderr', stderr) if stderr 110 | showDevModeWarning('stdout', stdout) if stdout 111 | 112 | # call a needed parser 113 | output = errorMode.neededOutput(stdout, stderr) 114 | messages = errorMode.parse output, {@disabledWarnings, textEditor} 115 | 116 | # correct file paths 117 | messages.forEach (message) -> 118 | if !(path.isAbsolute message.location.file) 119 | message.location.file = path.join curDir, message.location.file 120 | messages 121 | else 122 | # whoops, we're in trouble -- let's output as much as we can 123 | atom.notifications.addError "Failed to run #{command} with exit code #{exitCode}", 124 | detail: "with args:\n #{args.join(' ')}\nSee console for more information" 125 | dismissable: true 126 | console.log "stdout:" 127 | console.log stdout 128 | console.log "stderr:" 129 | console.log stderr 130 | [] 131 | .catch (error) -> 132 | console.log error 133 | atom.notifications.addError "Failed to run #{command}", 134 | detail: "#{error.message}" 135 | dismissable: true 136 | [] 137 | 138 | initCmd: (editingFile) => 139 | curDir = if editingFile? then path.dirname editingFile else __dirname 140 | cargoManifestPath = @locateCargo curDir 141 | if not @useCargo or not cargoManifestPath 142 | @decideErrorMode(curDir, 'rustc').then (mode) => 143 | mode.buildArguments(this, [editingFile, cargoManifestPath]).then (cmd) -> 144 | [cmd, mode] 145 | else 146 | @decideErrorMode(curDir, 'cargo').then (mode) => 147 | mode.buildArguments(this, cargoManifestPath).then (cmd) -> 148 | [cmd, mode] 149 | 150 | compilationFeatures: (cargo) => 151 | if @specifiedFeatures.length > 0 152 | if cargo 153 | ['--features', @specifiedFeatures.join(' ')] 154 | else 155 | result = [] 156 | cfgs = for f in @specifiedFeatures 157 | result.push ['--cfg', "feature=\"#{f}\""] 158 | result 159 | 160 | decideErrorMode: (curDir, commandMode) => 161 | # error mode is cached to avoid delays 162 | if @cachedErrorMode? and @allowedToCacheVersions 163 | Promise.resolve().then () => 164 | @cachedErrorMode 165 | else 166 | # current dir is set to handle overrides 167 | execOpts = 168 | cwd: curDir 169 | execOpts.timeout = Infinity if @disableExecTimeout 170 | atom_linter.exec(@rustcPath, ['--version'], execOpts).then (stdout) => 171 | try 172 | match = XRegExp.exec(stdout, @patternRustcVersion) 173 | if match 174 | nightlyWithJSON = match.nightly and match.date > '2016-08-08' 175 | stableWithJSON = not match.nightly and semver.gte(match.version, '1.12.0') 176 | canUseIntermediateJSON = nightlyWithJSON or stableWithJSON 177 | switch commandMode 178 | when 'cargo' 179 | canUseProperCargoJSON = (match.nightly and match.date >= '2016-10-10') or 180 | (match.beta or not match.nightly and semver.gte(match.version, '1.13.0')) 181 | if canUseProperCargoJSON 182 | errorModes.JSON_CARGO 183 | # this mode is used only through August till October, 2016 184 | else if canUseIntermediateJSON 185 | errorModes.FLAGS_JSON_CARGO 186 | else 187 | errorModes.OLD_CARGO 188 | when 'rustc' 189 | if canUseIntermediateJSON 190 | errorModes.JSON_RUSTC 191 | else 192 | errorModes.OLD_RUSTC 193 | else 194 | throw Error('rustc returned unexpected result: ' + stdout) 195 | .then (result) => 196 | @cachedErrorMode = result 197 | result 198 | 199 | locateCargo: (curDir) => 200 | root_dir = if /^win/.test process.platform then /^.:\\$/ else /^\/$/ 201 | directory = path.resolve curDir 202 | loop 203 | return path.join directory, @cargoManifestFilename if fs.existsSync path.join directory, @cargoManifestFilename 204 | break if root_dir.test directory 205 | directory = path.resolve path.join(directory, '..') 206 | return false 207 | 208 | module.exports = LinterRust 209 | -------------------------------------------------------------------------------- /lib/mode.coffee: -------------------------------------------------------------------------------- 1 | path = require 'path' 2 | 3 | atom_linter = require 'atom-linter' 4 | XRegExp = require 'xregexp' 5 | 6 | pattern = XRegExp('(?[^\n\r]+):(?\\d+):(?\\d+):\\s*\ 7 | (?\\d+):(?\\d+)\\s+\ 8 | ((?error|fatal error)|(?warning)|(?note|help)):\\s+\ 9 | (?.+?)[\n\r]+($|(?=[^\n\r]+:\\d+))', 's') 10 | 11 | parseOldMessages = (output, {disabledWarnings, textEditor}) -> 12 | elements = [] 13 | XRegExp.forEach output, pattern, (match) -> 14 | range = if match.from_col == match.to_col and match.from_line == match.to_line 15 | atom_linter.generateRange(textEditor, Number.parseInt(match.from_line, 10) - 1, Number.parseInt(match.from_col, 10) - 1) 16 | else 17 | [ 18 | [match.from_line - 1, match.from_col - 1], 19 | [match.to_line - 1, match.to_col - 1] 20 | ] 21 | level = if match.error then 'error' 22 | else if match.warning then 'warning' 23 | else if match.info then 'info' 24 | else if match.trace then 'trace' 25 | else if match.note then 'note' 26 | element = 27 | type: level 28 | message: match.message 29 | file: match.file 30 | range: range 31 | elements.push element 32 | buildMessages elements, disabledWarnings 33 | 34 | parseJsonMessages = (messages, {disabledWarnings}) -> 35 | elements = [] 36 | for input in messages 37 | continue unless input and input.spans 38 | primary_span = input.spans.find (span) -> span.is_primary 39 | continue unless primary_span 40 | while primary_span.expansion and primary_span.expansion.span 41 | primary_span = primary_span.expansion.span 42 | range = [ 43 | [primary_span.line_start - 1, primary_span.column_start - 1], 44 | [primary_span.line_end - 1, primary_span.column_end - 1] 45 | ] 46 | input.level = 'error' if input.level == 'fatal error' 47 | element = 48 | type: input.level 49 | message: input.message 50 | file: primary_span.file_name 51 | range: range 52 | children: input.children 53 | for span in input.spans 54 | unless span.is_primary 55 | element.children.push 56 | range: [ 57 | [span.line_start - 1, span.column_start - 1], 58 | [span.line_end - 1, span.column_end - 1] 59 | ] 60 | elements.push element 61 | buildMessages elements, disabledWarnings 62 | 63 | parseJsonOutput = (output, {disabledWarnings, additionalFilter} ) -> 64 | results = output.split('\n').map (message) -> 65 | message = message.trim() 66 | if message.startsWith '{' 67 | json = JSON.parse message 68 | if additionalFilter? 69 | additionalFilter(json) 70 | else 71 | json 72 | .filter (m) -> m? 73 | parseJsonMessages results, {disabledWarnings} 74 | 75 | buildMessages = (elements, disabledWarnings) -> 76 | messages = [] 77 | lastMessage = null 78 | for element in elements 79 | switch element.type 80 | when 'info', 'trace', 'note' 81 | # Add only if there is a last message 82 | if lastMessage 83 | lastMessage.reference = { 84 | file: element.file 85 | position: element.range 86 | } 87 | when 'warning' 88 | # If the message is warning and user enabled disabling warnings 89 | # Check if this warning is disabled 90 | if disabledWarnings and disabledWarnings.length > 0 91 | messageIsDisabledLint = false 92 | for disabledWarning in disabledWarnings 93 | # Find a disabled lint in warning message 94 | if element.message.indexOf(disabledWarning) >= 0 95 | messageIsDisabledLint = true 96 | lastMessage = null 97 | break 98 | if not messageIsDisabledLint 99 | lastMessage = constructMessage "Warning", element 100 | messages.push lastMessage 101 | else 102 | lastMessage = constructMessage "Warning" , element 103 | messages.push lastMessage 104 | when 'error', 'fatal error' 105 | lastMessage = constructMessage "Error", element 106 | messages.push lastMessage 107 | return messages 108 | 109 | constructMessage = (type, element) -> 110 | message = 111 | severity: type.toLowerCase() 112 | excerpt: element.message 113 | location: { 114 | file: element.file 115 | position: element.range 116 | } 117 | # children exists only in JSON messages 118 | if element.children 119 | for children in element.children 120 | if children.range 121 | # NOTE: Will only save the first valid reference 122 | message.reference = { 123 | file: element.file 124 | position: children.range 125 | } 126 | break 127 | message 128 | 129 | buildRustcArguments = (linter, paths) -> 130 | [editingFile, cargoManifestPath] = paths 131 | Promise.resolve().then () => 132 | rustcArgs = switch linter.rustcBuildTest 133 | when true then ['--cfg', 'test'] 134 | else [] 135 | rustcArgs = rustcArgs.concat ['--color', 'never'] 136 | cmd = [linter.rustcPath] 137 | .concat rustcArgs 138 | if cargoManifestPath 139 | cmd.push '-L' 140 | cmd.push path.join path.dirname(cargoManifestPath), linter.cargoDependencyDir 141 | compilationFeatures = linter.compilationFeatures(false) 142 | cmd = cmd.concat compilationFeatures if compilationFeatures 143 | cmd = cmd.concat [editingFile] 144 | [editingFile, cmd] 145 | 146 | cachedUsingMultitoolForClippy = null 147 | 148 | buildCargoArguments = (linter, cargoManifestPath) -> 149 | buildCargoPath = (cargoPath, cargoCommand) -> 150 | # the result is cached to avoid delays 151 | if cachedUsingMultitoolForClippy? and linter.allowedToCacheVersions 152 | Promise.resolve().then () => 153 | cachedUsingMultitoolForClippy 154 | else 155 | # Decide if should use older multirust or newer rustup 156 | usingMultitoolForClippy = 157 | atom_linter.exec 'rustup', ['--version'], {ignoreExitCode: true} 158 | .then -> 159 | result: true, tool: 'rustup' 160 | .catch -> 161 | # Try to use older multirust at least 162 | atom_linter.exec 'multirust', ['--version'], {ignoreExitCode: true} 163 | .then -> 164 | result: true, tool: 'multirust' 165 | .catch -> 166 | result: false 167 | usingMultitoolForClippy.then (canUseMultirust) -> 168 | if cargoCommand == 'clippy' and canUseMultirust.result 169 | [canUseMultirust.tool, 'run', 'nightly', 'cargo'] 170 | else 171 | [cargoPath] 172 | .then (cached) => 173 | cachedUsingMultitoolForClippy = cached 174 | cached 175 | 176 | cargoArgs = switch linter.cargoCommand 177 | when 'check' then ['check'] 178 | when 'check all' then ['check', '--all'] 179 | when 'check tests' then ['check', '--tests'] 180 | when 'test' then ['test', '--no-run'] 181 | when 'test all' then ['test', '--no-run', '--all'] 182 | when 'rustc' then ['rustc', '--color', 'never'] 183 | when 'clippy' then ['clippy'] 184 | else ['build'] 185 | 186 | compilationFeatures = linter.compilationFeatures(true) 187 | buildCargoPath(linter.cargoPath, linter.cargoCommand).then (cmd) -> 188 | cmd = cmd 189 | .concat cargoArgs 190 | .concat ['-j', linter.jobsNumber] 191 | cmd = cmd.concat compilationFeatures if compilationFeatures 192 | cmd.push "--manifest-path=#{cargoManifestPath}" 193 | [cargoManifestPath, cmd] 194 | 195 | # These define the behabiour of each error mode linter-rust has 196 | errorModes = 197 | JSON_RUSTC: 198 | neededOutput: (stdout, stderr) -> 199 | stderr 200 | 201 | parse: (output, options) => 202 | parseJsonOutput output, options 203 | 204 | buildArguments: (linter, file) -> 205 | buildRustcArguments(linter, file).then (cmd_res) -> 206 | [file, cmd] = cmd_res 207 | cmd = cmd.concat ['--error-format=json'] 208 | [file, cmd] 209 | 210 | JSON_CARGO: 211 | neededOutput: (stdout, stderr) -> 212 | stdout 213 | 214 | parse: (output, options) -> 215 | options.additionalFilter = (json) -> 216 | if json? and json.reason == "compiler-message" 217 | json.message 218 | parseJsonOutput output, options 219 | 220 | buildArguments: (linter, file) -> 221 | buildCargoArguments(linter, file).then (cmd_res) -> 222 | [file, cmd] = cmd_res 223 | cmd = cmd.concat ['--message-format', 'json'] 224 | [file, cmd] 225 | 226 | FLAGS_JSON_CARGO: 227 | neededOutput: (stdout, stderr) -> 228 | stderr 229 | 230 | parse: parseJsonOutput 231 | 232 | buildArguments: buildCargoArguments 233 | 234 | OLD_RUSTC: 235 | neededOutput: (stdout, stderr) -> 236 | stderr 237 | 238 | parse: parseOldMessages 239 | 240 | buildArguments: buildRustcArguments 241 | 242 | OLD_CARGO: 243 | neededOutput: (stdout, stderr) -> 244 | stderr 245 | 246 | parse: parseOldMessages 247 | 248 | buildArguments: buildCargoArguments 249 | 250 | module.exports = errorModes 251 | -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "linter-rust", 3 | "version": "1.0.1", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "@babel/runtime-corejs3": { 8 | "version": "7.8.4", 9 | "resolved": "https://registry.npmjs.org/@babel/runtime-corejs3/-/runtime-corejs3-7.8.4.tgz", 10 | "integrity": "sha512-+wpLqy5+fbQhvbllvlJEVRIpYj+COUWnnsm+I4jZlA8Lo7/MJmBhGTCHyk1/RWfOqBRJ2MbadddG6QltTKTlrg==", 11 | "requires": { 12 | "core-js-pure": "^3.0.0", 13 | "regenerator-runtime": "^0.13.2" 14 | } 15 | }, 16 | "atom-linter": { 17 | "version": "10.0.0", 18 | "resolved": "https://registry.npmjs.org/atom-linter/-/atom-linter-10.0.0.tgz", 19 | "integrity": "sha1-0nu3Tl+PCKdKQL6ynuGlDZdUPIk=", 20 | "requires": { 21 | "named-js-regexp": "^1.3.1", 22 | "sb-exec": "^4.0.0", 23 | "sb-promisify": "^2.0.1", 24 | "tmp": "~0.0.28" 25 | } 26 | }, 27 | "atom-package-deps": { 28 | "version": "5.1.0", 29 | "resolved": "https://registry.npmjs.org/atom-package-deps/-/atom-package-deps-5.1.0.tgz", 30 | "integrity": "sha512-RGktH8NSFBJ5rdwuta3M7DbFdDr1EgrXo7uW7DQR/+lWJZcrfH2yxobnSdb/g1JM1tTvLyRYmZYOeRJGqQ9UGw==", 31 | "requires": { 32 | "sb-fs": "^4.0.0", 33 | "semver": "^6.0.0" 34 | }, 35 | "dependencies": { 36 | "semver": { 37 | "version": "6.3.0", 38 | "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", 39 | "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==" 40 | } 41 | } 42 | }, 43 | "consistent-env": { 44 | "version": "1.3.1", 45 | "resolved": "https://registry.npmjs.org/consistent-env/-/consistent-env-1.3.1.tgz", 46 | "integrity": "sha1-9oI018afxt2WVviuI0Kc4EmbZfs=", 47 | "requires": { 48 | "lodash.uniq": "^4.5.0" 49 | } 50 | }, 51 | "core-js-pure": { 52 | "version": "3.6.4", 53 | "resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.6.4.tgz", 54 | "integrity": "sha512-epIhRLkXdgv32xIUFaaAry2wdxZYBi6bgM7cB136dzzXXa+dFyRLTZeLUJxnd8ShrmyVXBub63n2NHo2JAt8Cw==" 55 | }, 56 | "is-utf8": { 57 | "version": "0.2.1", 58 | "resolved": "https://registry.npmjs.org/is-utf8/-/is-utf8-0.2.1.tgz", 59 | "integrity": "sha1-Sw2hRCEE0bM2NA6AeX6GXPOffXI=" 60 | }, 61 | "lodash.uniq": { 62 | "version": "4.5.0", 63 | "resolved": "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz", 64 | "integrity": "sha1-0CJTc662Uq3BvILklFM5qEJ1R3M=" 65 | }, 66 | "named-js-regexp": { 67 | "version": "1.3.4", 68 | "resolved": "https://registry.npmjs.org/named-js-regexp/-/named-js-regexp-1.3.4.tgz", 69 | "integrity": "sha512-wvbB+afegWlmc3/bXd+S1DDLPGcyARWVvYCOntQwBRhKgwSZewk8zolExzyLrXT70xhFkmRYQGigXFaTgtHqjA==" 70 | }, 71 | "os-tmpdir": { 72 | "version": "1.0.2", 73 | "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", 74 | "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=" 75 | }, 76 | "regenerator-runtime": { 77 | "version": "0.13.3", 78 | "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.3.tgz", 79 | "integrity": "sha512-naKIZz2GQ8JWh///G7L3X6LaQUAMp2lvb1rvwwsURe/VXwD6VMfr+/1NuNw3ag8v2kY1aQ/go5SNn79O9JU7yw==" 80 | }, 81 | "sb-exec": { 82 | "version": "4.0.0", 83 | "resolved": "https://registry.npmjs.org/sb-exec/-/sb-exec-4.0.0.tgz", 84 | "integrity": "sha1-RnR/DfFiYmwW6/D+pCJFrRqoWco=", 85 | "requires": { 86 | "consistent-env": "^1.2.0", 87 | "lodash.uniq": "^4.5.0", 88 | "sb-npm-path": "^2.0.0" 89 | } 90 | }, 91 | "sb-fs": { 92 | "version": "4.0.0", 93 | "resolved": "https://registry.npmjs.org/sb-fs/-/sb-fs-4.0.0.tgz", 94 | "integrity": "sha512-UjjIHC4uahPWvKYqgknvFCCJ11S0oDahz+nsmyTCAmARKto31aoE+Lu7GGGK0nogengJEKGzFdh46ho5+IL88Q==", 95 | "requires": { 96 | "strip-bom-buf": "^1.0.0" 97 | } 98 | }, 99 | "sb-memoize": { 100 | "version": "1.0.2", 101 | "resolved": "https://registry.npmjs.org/sb-memoize/-/sb-memoize-1.0.2.tgz", 102 | "integrity": "sha1-EoN1xi3bnMT/qQXQxaWXwZuurY4=" 103 | }, 104 | "sb-npm-path": { 105 | "version": "2.0.0", 106 | "resolved": "https://registry.npmjs.org/sb-npm-path/-/sb-npm-path-2.0.0.tgz", 107 | "integrity": "sha1-D2zCzzcd68p9k27Xa31MPMHrPVg=", 108 | "requires": { 109 | "sb-memoize": "^1.0.2", 110 | "sb-promisify": "^2.0.1" 111 | } 112 | }, 113 | "sb-promisify": { 114 | "version": "2.0.2", 115 | "resolved": "https://registry.npmjs.org/sb-promisify/-/sb-promisify-2.0.2.tgz", 116 | "integrity": "sha1-QnelR1RIiqlnXYhuNU24lMm9yYE=" 117 | }, 118 | "semver": { 119 | "version": "7.1.3", 120 | "resolved": "https://registry.npmjs.org/semver/-/semver-7.1.3.tgz", 121 | "integrity": "sha512-ekM0zfiA9SCBlsKa2X1hxyxiI4L3B6EbVJkkdgQXnSEEaHlGdvyodMruTiulSRWMMB4NeIuYNMC9rTKTz97GxA==" 122 | }, 123 | "strip-bom-buf": { 124 | "version": "1.0.0", 125 | "resolved": "https://registry.npmjs.org/strip-bom-buf/-/strip-bom-buf-1.0.0.tgz", 126 | "integrity": "sha1-HLRar1dTD0yvhsf3UXnSyaUd1XI=", 127 | "requires": { 128 | "is-utf8": "^0.2.1" 129 | } 130 | }, 131 | "tmp": { 132 | "version": "0.0.33", 133 | "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", 134 | "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", 135 | "requires": { 136 | "os-tmpdir": "~1.0.2" 137 | } 138 | }, 139 | "xregexp": { 140 | "version": "4.3.0", 141 | "resolved": "https://registry.npmjs.org/xregexp/-/xregexp-4.3.0.tgz", 142 | "integrity": "sha512-7jXDIFXh5yJ/orPn4SXjuVrWWoi4Cr8jfV1eHv9CixKSbU+jY4mxfrBwAuDvupPNKpMUY+FeIqsVw/JLT9+B8g==", 143 | "requires": { 144 | "@babel/runtime-corejs3": "^7.8.3" 145 | } 146 | } 147 | } 148 | } 149 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "linter-rust", 3 | "main": "./lib/init", 4 | "version": "1.0.1", 5 | "private": true, 6 | "description": "Lint Rust-files, using rustc and/or cargo", 7 | "repository": { 8 | "type": "git", 9 | "url": "https://github.com/AtomLinter/linter-rust.git" 10 | }, 11 | "license": "MIT", 12 | "engines": { 13 | "atom": ">=1.33.0 <2.0.0" 14 | }, 15 | "providedServices": { 16 | "linter": { 17 | "versions": { 18 | "2.0.0": "provideLinter" 19 | } 20 | } 21 | }, 22 | "dependencies": { 23 | "atom-linter": "10.0.0", 24 | "atom-package-deps": "5.1.0", 25 | "semver": "7.1.3", 26 | "xregexp": "4.3.0" 27 | }, 28 | "renovate": { 29 | "extends": [ 30 | "config:base" 31 | ], 32 | "semanticCommits": true, 33 | "rangeStrategy": "pin", 34 | "packageRules": [ 35 | { 36 | "packagePatterns": [ 37 | "^eslint" 38 | ], 39 | "groupName": "ESLint packages" 40 | } 41 | ] 42 | }, 43 | "package-deps": [ 44 | "linter:2.0.0" 45 | ] 46 | } 47 | -------------------------------------------------------------------------------- /spec/parse-spec.coffee: -------------------------------------------------------------------------------- 1 | errorModes = require '../lib/mode' 2 | LinterRust = require '../lib/linter-rust' 3 | 4 | linter = new LinterRust() 5 | 6 | describe "errorModes::OLD_RUSTC::parse", -> 7 | it "should return 0 messages for an empty string", -> 8 | expect(errorModes.OLD_RUSTC.parse('', {})).toEqual([]) 9 | 10 | it "should properly parse one line error message", -> 11 | expect(errorModes.OLD_RUSTC.parse('my/awesome file.rs:1:2: 3:4 error: my awesome text\n', {})) 12 | .toEqual([{ 13 | severity: 'error' 14 | excerpt: 'my awesome text' 15 | location: 16 | file: 'my/awesome file.rs' 17 | position: [[0, 1], [2, 3]] 18 | }]) 19 | 20 | it "should properly parse one line warning message", -> 21 | expect(errorModes.OLD_RUSTC.parse('foo:33:44: 22:33 warning: äüö<>\n', {})) 22 | .toEqual([{ 23 | severity: 'warning', 24 | excerpt: 'äüö<>' 25 | location: 26 | file: 'foo' 27 | position: [[32, 43], [21, 32]] 28 | }]) 29 | 30 | it "should return messages with a range of at least one character", -> 31 | editor = atom.workspace.buildTextEditor() 32 | editor.setText 'fn main() {\nprintln!("Hi test");}\n' 33 | # expect(editor.getPath()).toContain 'c.coffee' 34 | expect(errorModes.OLD_RUSTC.parse('foo:1:1: 1:1 error: text\n', {textEditor: editor})) 35 | .toEqual([{ 36 | severity: 'error' 37 | excerpt: 'text' 38 | location: 39 | file: 'foo' 40 | position: [[0, 0], [0, 2]] 41 | }]) 42 | expect(errorModes.OLD_RUSTC.parse('foo:2:1: 2:1 error: text\n', {textEditor: editor})) 43 | .toEqual([{ 44 | severity: 'error' 45 | excerpt: 'text' 46 | location: 47 | file: 'foo' 48 | position: [[1, 0], [1, 7]] 49 | }]) 50 | 51 | it "should properly parse multiline messages", -> 52 | expect(errorModes.OLD_RUSTC.parse('bar:1:2: 3:4 error: line one\n\ 53 | two\n', {})) 54 | .toEqual([ 55 | { 56 | severity: 'error', 57 | excerpt: 'line one\ntwo', 58 | location: { 59 | file: 'bar', 60 | position: [[0, 1], [2, 3]] 61 | } 62 | } 63 | ]) 64 | expect(errorModes.OLD_RUSTC.parse('bar:1:2: 3:4 error: line one\n\ 65 | two\n\ 66 | foo:1:1: 1:2 warning: simple line\n', {})) 67 | .toEqual([ 68 | { 69 | severity: 'error', 70 | excerpt: 'line one\ntwo', 71 | location: { 72 | file: 'bar', 73 | position: [[0, 1], [2, 3]] 74 | } 75 | }, 76 | { 77 | severity: 'warning', 78 | excerpt: 'simple line', 79 | location: { 80 | file: 'foo', 81 | position: [[0, 0], [0, 1]] 82 | } 83 | } 84 | ]) 85 | expect(errorModes.OLD_RUSTC.parse('bar:1:2: 3:4 error: line one\n\ 86 | two\n\ 87 | three\n\ 88 | foo:1 shouldnt match', {})) 89 | .toEqual([ 90 | { 91 | severity: 'error', 92 | excerpt: 'line one\ntwo\nthree', 93 | location: { 94 | file: 'bar', 95 | position: [[0, 1], [2, 3]] 96 | } 97 | } 98 | ]) 99 | 100 | it "should also cope with windows line breaks", -> 101 | expect(errorModes.OLD_RUSTC.parse('a:1:2: 3:4 error: a\r\nb\n', {})[0].excerpt) 102 | .toEqual('a\r\nb') 103 | 104 | multi = errorModes.OLD_RUSTC.parse('a:1:2: 3:4 error: a\n\rb\n\rx:1:2: 3:4 error: asd\r\n', {}) 105 | expect(multi[0].excerpt).toEqual('a\n\rb') 106 | expect(multi[1].excerpt).toEqual('asd') 107 | 108 | it "should not throw an error with extra whitespace in paths", -> 109 | buildLinterWithWhitespacePath = () -> 110 | atom.config.set "linter-rust.rustc", "rustc\n" 111 | atom.config.set "linter-rust.cargo", "cargo\n" 112 | new LinterRust() 113 | 114 | resetPath = () -> 115 | atom.config.set "linter-rust.rustc", "rustc" 116 | atom.config.set "linter-rust.cargo", "cargo" 117 | 118 | expect(buildLinterWithWhitespacePath).not.toThrow() 119 | resetPath() 120 | --------------------------------------------------------------------------------