mattn / efm-langserver

General purpose Language Server
MIT License
1.36k stars 61 forks source link

Unable to use gcc as linter for Ada #236

Closed TamaMcGlinn closed 1 year ago

TamaMcGlinn commented 1 year ago

I am trying to use efm-langserver with this config in NeoVim:

require"lspconfig".efm.setup {
    init_options = {documentFormatting = true},
    settings = {
        rootMarkers = {".git/"},
        languages = {
            lua = {{formatCommand = "lua-format -i", formatStdin = true}},
            ada = {
                {
                    lintCommand = 'gcc -c',
                    lintIgnoreExitCode = true,
                    lintFormats = {
                        '%f:%l:%c: %trror: %m', '%f:%l:%c: %tarning: %m',
                        '%f:%l:%c: %tote: %m'
                    }
                }
            }
        }
    }
}

After setting ~/.config/efm-langserver/config.yaml accordingly to get debug output:

version: 2
log-file: /home/tama/efmlog.txt
log-level: 3

And loading a single Ada file with two errors present:

with Ada.Text_IO;  use Ada.Text_IO;

procedure Main is
   procedure Foo return Boolean is
   begin
      null;
   end NotFoo;
begin
   Put_Line ("Hello world");
end Main;

My ~/efmlog.txt now correctly notes the two errors, but they still don't show up as lint messages.

2022/11/08 11:34:08 gcc -c /home/tama/code/ada/testit/src/main.adb: main.adb:4:04: error: "procedure" should be "function"
main.adb:7:04: error: "end Foo;" expected

Any ideas?

mattn commented 1 year ago

As far as I can see, the pattern & input seems good to me.

https://reviewdog.github.io/errorformat-playground/?efms=%25f%3A%25l%3A%25c%3A+%25trror%3A+%25m%0A%25f%3A%25l%3A%25c%3A+%25tarning%3A+%25m%0A%25f%3A%25l%3A%25c%3A+%25tote%3A+%25m%27&text=main.adb%3A7%3A04%3A+error%3A+%22end+Foo%3B%22+expected

TamaMcGlinn commented 1 year ago

so, does efm expect the linter to output a piece of json with a filename etc? I see that the filename is not complete; it doesn't include the directory from the root of the repository, but is instead just main.adb. Is that a problem?

mattn commented 1 year ago

I can't make sure what is wrong since I don't have gcc supported ada. Could you please try linter-stdin?

TamaMcGlinn commented 1 year ago

As far as I can see, the full path rather than just the filename is the only difference. So I am going to try wrapping gcc in a script to output the full path rather than just the filename.

Tue Nov 08-15:48:39 - tama@vm001954:~/code/ada/testit 
$ gcc -c src/main.cpp 
src/main.cpp:1:1: error: '::main' must return 'int'
    1 | void main(){
      | ^~~~
src/main.cpp: In function 'int main()':
src/main.cpp:2:10: error: 'something' was not declared in this scope
    2 |   return something;
      |          ^~~~~~~~~
Tue Nov 08-15:48:44 - tama@vm001954:~/code/ada/testit 
$ gcc -c src/main.adb
main.adb:4:04: error: "procedure" should be "function"
main.adb:7:04: error: "end Foo;" expected
TamaMcGlinn commented 1 year ago

Ok, this works:

#!/usr/bin/env bash
FULLPATH=$(readlink -f "$1")
FILE_NAME=${FULLPATH##*/}
gcc -c "$@" 2>&1 | sed "s@${FILE_NAME}@${FULLPATH}@"

I've named the script gcc_lint, put it in $PATH and replaced gcc -c with gcc_lint and it works. So the problem is that gcc, specifically when compiling Ada files, outputs only the file's name, without any part of the path, whereas efm requires the full path to the file to be output.

TamaMcGlinn commented 1 year ago

For future readers, here is a more advanced version I call gprbuild_lint which also works for .ads specs:

#!/usr/bin/env bash

# Original reason for the script: gcc (and gprbuild) output only the filename,
# not the full path as required by efm-langserver
FULLPATH=$(readlink -f "$1")
FILE_NAME=${FULLPATH##*/}

# gcc -c only works on package bodies (*.adb)
if [[ $1 == *.adb ]]; then
  # when treating a .adb file, we can pass -c [thefile] to only compile that file
  # this is much quicker than compiling the whole project
  COMPILE="-c $1"
else
  CORRESPONDING_ADB="${1%%.ads}.adb"
  if test -f $CORRESPONDING_ADB; then
    # compile the corresponding .adb file
    COMPILE="-c $CORRESPONDING_ADB"
  else
    # fallback: compile the whole project
    COMPILE=""
  fi
fi

# Find *.gpr upwards from source file
# TODO allow overriding GPRFILE from neovim, for multi-gpr projects - in that case, do not search
DIR=$(dirname "$FULLPATH")
while
  GPRFILE=$(find "$DIR"/ -maxdepth 1 -type f -iname "*.gpr")
  [[ -z $GPRFILE ]] && [[ "$DIR" != "/" ]]
do DIR=$(dirname "$DIR"); done

if [[ -n $GPRFILE ]]; then
  PROJECT="-P $GPRFILE"
fi

COMMAND="gprbuild -k $PROJECT $COMPILE"
$COMMAND 2>&1 | sed "s@${FILE_NAME}@${FULLPATH}@"

# # Debugging line to get info directly from linter into file
# echo "src/testit-example.ads:02:00: Note: $COMMAND"

@mattn regarding the TODO; have you ever passed information before directly from NeoVim into a linter on each call? This is because the project file could change at any time (using the nvim-lsp-gpr-selector plugin). I could use neovim-remote in the linter to access a global in the NeoVim instance, but it would be a pretty horrible solution.

mattn commented 1 year ago

Sorry, I don't use NeoVim.