├── .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 [](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.
31 | - Use **build** to simply compile the code.
32 | - Use **check** for fast linting (does not build the project).
33 | - Use **check all** for fast linting of all packages in the project.
34 | - Use **check tests** to also include \`#[cfg(test)]\` code in linting.
35 | - Use **clippy** to increase amount of available lints (you need to install \`clippy\`).
36 | - Use **test** to run tests (note that once the tests are built, lints stop showing).
37 | - Use **test all** run tests for all packages in the project.
38 | - Use **rustc** for linting with Rust pre-1.23.
39 |
"""
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 |
--------------------------------------------------------------------------------