diff --git a/lib/ruby_lsp/requests/diagnostics.rb b/lib/ruby_lsp/requests/diagnostics.rb index 856fd6212..1e705c226 100644 --- a/lib/ruby_lsp/requests/diagnostics.rb +++ b/lib/ruby_lsp/requests/diagnostics.rb @@ -42,27 +42,61 @@ def initialize(document) sig { override.returns(T.nilable(T.all(T::Array[Interface::Diagnostic], Object))) } def perform + diagnostics = [] + diagnostics.concat(syntax_error_diagnostics, syntax_warning_diagnostics) + # Running RuboCop is slow, so to avoid excessive runs we only do so if the file is syntactically valid - return syntax_error_diagnostics if @document.syntax_error? - return [] unless defined?(Support::RuboCopDiagnosticsRunner) + return diagnostics if @document.syntax_error? + + diagnostics.concat( + Support::RuboCopDiagnosticsRunner.instance.run( + @uri, + @document, + ).map!(&:to_lsp_diagnostic), + ) if defined?(Support::RuboCopDiagnosticsRunner) - Support::RuboCopDiagnosticsRunner.instance.run(@uri, @document).map!(&:to_lsp_diagnostic) + diagnostics end private - sig { returns(T.nilable(T::Array[Interface::Diagnostic])) } + sig { returns(T::Array[Interface::Diagnostic]) } + def syntax_warning_diagnostics + @document.parse_result.warnings.map do |warning| + location = warning.location + + Interface::Diagnostic.new( + source: "Prism", + message: warning.message, + severity: Constant::DiagnosticSeverity::WARNING, + range: Interface::Range.new( + start: Interface::Position.new( + line: location.start_line - 1, + character: location.start_column, + ), + end: Interface::Position.new( + line: location.end_line - 1, + character: location.end_column, + ), + ), + ) + end + end + + sig { returns(T::Array[Interface::Diagnostic]) } def syntax_error_diagnostics @document.parse_result.errors.map do |error| + location = error.location + Interface::Diagnostic.new( range: Interface::Range.new( start: Interface::Position.new( - line: error.location.start_line - 1, - character: error.location.start_column, + line: location.start_line - 1, + character: location.start_column, ), end: Interface::Position.new( - line: error.location.end_line - 1, - character: error.location.end_column, + line: location.end_line - 1, + character: location.end_column, ), ), message: error.message, diff --git a/test/expectations/diagnostics/syntax_diagnostics.exp.json b/test/expectations/diagnostics/syntax_diagnostics.exp.json new file mode 100644 index 000000000..afe0fa147 --- /dev/null +++ b/test/expectations/diagnostics/syntax_diagnostics.exp.json @@ -0,0 +1,110 @@ +{ + "result": [ + { + "range": { + "start": { + "line": 7, + "character": 0 + }, + "end": { + "line": 7, + "character": 0 + } + }, + "severity": 1, + "source": "Prism", + "message": "unexpected end of file, assuming it is closing the parent top level context" + }, + { + "range": { + "start": { + "line": 7, + "character": 0 + }, + "end": { + "line": 7, + "character": 0 + } + }, + "severity": 1, + "source": "Prism", + "message": "expected an `end` to close the `def` statement" + }, + { + "range": { + "start": { + "line": 0, + "character": 2 + }, + "end": { + "line": 0, + "character": 3 + } + }, + "severity": 2, + "source": "Prism", + "message": "ambiguous first argument; put parentheses or a space even after `+` operator" + }, + { + "range": { + "start": { + "line": 1, + "character": 2 + }, + "end": { + "line": 1, + "character": 3 + } + }, + "severity": 2, + "source": "Prism", + "message": "ambiguous first argument; put parentheses or a space even after `-` operator" + }, + { + "range": { + "start": { + "line": 2, + "character": 2 + }, + "end": { + "line": 2, + "character": 3 + } + }, + "severity": 2, + "source": "Prism", + "message": "ambiguous `*` has been interpreted as an argument prefix" + }, + { + "range": { + "start": { + "line": 3, + "character": 2 + }, + "end": { + "line": 3, + "character": 3 + } + }, + "severity": 2, + "source": "Prism", + "message": "ambiguous `/`; wrap regexp in parentheses or add a space after `/` operator" + }, + { + "range": { + "start": { + "line": 4, + "character": 7 + }, + "end": { + "line": 4, + "character": 10 + } + }, + "severity": 2, + "source": "Prism", + "message": "END in method; use at_exit" + } + ], + "params": [] +} diff --git a/test/fixtures/syntax_diagnostics.rb b/test/fixtures/syntax_diagnostics.rb new file mode 100644 index 000000000..14b262da5 --- /dev/null +++ b/test/fixtures/syntax_diagnostics.rb @@ -0,0 +1,7 @@ +b +a +b -a +b *a +b /a/ +def m; END{}; end + +def foo diff --git a/test/requests/diagnostics_expectations_test.rb b/test/requests/diagnostics_expectations_test.rb index 4a6b47d12..52df3d8b6 100644 --- a/test/requests/diagnostics_expectations_test.rb +++ b/test/requests/diagnostics_expectations_test.rb @@ -22,7 +22,7 @@ def run_expectations(source) assert_empty(stdout) # On Windows, RuboCop will complain that the file is missing a carriage return at the end. We need to ignore these - T.must(result).reject { |diagnostic| diagnostic.code == "Layout/EndOfLine" } + T.must(result).reject { |diagnostic| diagnostic.source == "RuboCop" && diagnostic.code == "Layout/EndOfLine" } end def assert_expectations(source, expected) @@ -32,7 +32,7 @@ def assert_expectations(source, expected) actual.each do |diagnostic| attributes = diagnostic.attributes - attributes[:data][:code_actions].each do |code_action| + attributes.fetch(:data, {}).fetch(:code_actions, []).each do |code_action| code_action_changes = code_action.attributes[:edit].attributes[:documentChanges] code_action_changes.each do |code_action_change| code_action_change @@ -51,7 +51,7 @@ def map_diagnostics(diagnostics) diagnostics.map do |diagnostic| LanguageServer::Protocol::Interface::Diagnostic.new( message: diagnostic["message"], - source: "RuboCop", + source: diagnostic["source"], code: diagnostic["code"], severity: diagnostic["severity"], code_description: diagnostic["codeDescription"],