Skip to content
Merged

Fixes #327

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .github/workflows/pr-verify.yml
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ jobs:
if: runner.os =='macOS'
- name: Checkout Source
uses: actions/checkout@v4
with:
submodules: true
- name: Install Node ${{ matrix.node }}
uses: actions/setup-node@v4
with:
Expand Down
1 change: 1 addition & 0 deletions apps/erlangbridge/src/lsp_parse.erl
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
-module(lsp_parse).
-export([parse_source_file/2, parse_config_file/2, get_include_path/1, get_include_path_no_build/1, scan_source_file/2]).
-compile(nowarn_deprecated_catch).

%% @doc
%% @param File is a reference to the real file (file opened by editor)
Expand Down
1 change: 1 addition & 0 deletions apps/erlangbridge/src/lsp_signature_doc_layout.erl
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
-module(lsp_signature_doc_layout).

-export([module/2]).
-compile({nowarn_unused_function, {signatures_sample, 0}}).

-include_lib("xmerl/include/xmerl.hrl").

Expand Down
166 changes: 83 additions & 83 deletions apps/erlangbridge/src/lsp_syntax.erl
Original file line number Diff line number Diff line change
Expand Up @@ -183,91 +183,91 @@ lint(FileSyntaxTree, File) ->
% max_lc(_, Acc) ->
% Acc.

check_if_remote_fun_exists(RootWorkspace, FnModule, FnName, FnArity, {Line, Column, LE,LC}) ->
% check if module is under workspace
case gen_lsp_doc_server:get_module_file(FnModule) of
undefined -> [];
TargetFile ->
case string:str(TargetFile, RootWorkspace) of
1 ->
case available_functions(TargetFile, FnName, FnArity) of
{[],[],[]} -> % no match => function doesn't exists
[
#{info =>
#{line => Line,
message => lsp_utils:to_binary("function ~p:~p/~p undefined", [FnModule,FnName,FnArity]),
character => Column,
line_end => LE,
character_end => LC},
type => <<"error">>,
file => lsp_utils:to_binary(TargetFile),
source => ?LINTER,
correlation_data => #{ action => <<"create_function">>, arguments => [FnModule, FnName, FnArity]}
}
];

{MatchFns, _, _} when length(MatchFns) > 0 -> % function is found, so no error
[];
{_, DefMatchFns, _} when length(DefMatchFns) > 0 -> % function is found, but not exported
[
#{info =>
#{line => Line,
message =>
lsp_utils:to_binary(lsp_utils:to_string("function ~s:~s/~p is missing in export.",[FnModule, FnName, FnArity])),
character => Column,
line_end => LE,
character_end => LC},
type => <<"error">>,
file => lsp_utils:to_binary(TargetFile),
source => ?LINTER,
correlation_data => #{ action => <<"export_function">>, arguments => [FnModule, FnName, FnArity]}
}
];
{[], [], NotMatchFns} when length(NotMatchFns) > 0 ->
%funtions with other arity
AvailableFns = lists:flatten(lists:join(",",
lists:filtermap(fun
({exported, Fn, Fa}) -> {true, lsp_utils:to_string("~s/~p\n",[Fn, Fa])};
(_) -> false
end, NotMatchFns))),
Message = lsp_utils:to_binary("function ~s:~s/~p arity mismatch.\navailable arity are :\n ~s\n",
[FnModule, FnName, FnArity, AvailableFns]),
[
#{info =>
#{line => Line,
message => Message,
character => Column,
line_end => LE,
character_end => LC},
type => <<"error">>,
file => lsp_utils:to_binary(TargetFile),
source => ?LINTER
}];

_ -> []
end;
_ -> []
end
end.
%check_if_remote_fun_exists(RootWorkspace, FnModule, FnName, FnArity, {Line, Column, LE,LC}) ->
% % check if module is under workspace
% case gen_lsp_doc_server:get_module_file(FnModule) of
% undefined -> [];
% TargetFile ->
% case string:str(TargetFile, RootWorkspace) of
% 1 ->
% case available_functions(TargetFile, FnName, FnArity) of
% {[],[],[]} -> % no match => function doesn't exists
% [
% #{info =>
% #{line => Line,
% message => lsp_utils:to_binary("function ~p:~p/~p undefined", [FnModule,FnName,FnArity]),
% character => Column,
% line_end => LE,
% character_end => LC},
% type => <<"error">>,
% file => lsp_utils:to_binary(TargetFile),
% source => ?LINTER,
% correlation_data => #{ action => <<"create_function">>, arguments => [FnModule, FnName, FnArity]}
% }
% ];
%
% {MatchFns, _, _} when length(MatchFns) > 0 -> % function is found, so no error
% [];
% {_, DefMatchFns, _} when length(DefMatchFns) > 0 -> % function is found, but not exported
% [
% #{info =>
% #{line => Line,
% message =>
% lsp_utils:to_binary(lsp_utils:to_string("function ~s:~s/~p is missing in export.",[FnModule, FnName, FnArity])),
% character => Column,
% line_end => LE,
% character_end => LC},
% type => <<"error">>,
% file => lsp_utils:to_binary(TargetFile),
% source => ?LINTER,
% correlation_data => #{ action => <<"export_function">>, arguments => [FnModule, FnName, FnArity]}
% }
% ];
% {[], [], NotMatchFns} when length(NotMatchFns) > 0 ->
% %funtions with other arity
% AvailableFns = lists:flatten(lists:join(",",
% lists:filtermap(fun
% ({exported, Fn, Fa}) -> {true, lsp_utils:to_string("~s/~p\n",[Fn, Fa])};
% (_) -> false
% end, NotMatchFns))),
% Message = lsp_utils:to_binary("function ~s:~s/~p arity mismatch.\navailable arity are :\n ~s\n",
% [FnModule, FnName, FnArity, AvailableFns]),
% [
% #{info =>
% #{line => Line,
% message => Message,
% character => Column,
% line_end => LE,
% character_end => LC},
% type => <<"error">>,
% file => lsp_utils:to_binary(TargetFile),
% source => ?LINTER
% }];
%
% _ -> []
% end;
% _ -> []
% end
% end.

% get functions that match and nearly match (by arity)
available_functions(File, FunName, FunArity) ->
Functions =fold_in_syntax_tree(fun
({function, {_, _}, FName, FnArity, _}, _CurrentFile, {M, DM, NM}) when FName =:= FunName, FnArity =:= FunArity ->
{M, [{definition_match, FName, FunArity} | DM], NM};
({function, {_, _}, FName, Arity, _}, _CurrentFile, {M, DM, NM}) when FName =:= FunName ->
{M, DM, [{definition, FName, Arity} | NM]};
({attribute, {_L, _C}, export, Exports}, _CurrentFile, {M, DM, NM}=Acc) ->
case lists:keyfind(FunName, 1, Exports) of
{_, Arity} when Arity =:= FunArity -> {[{exported_match, FunName, FunArity} | M], DM, NM};
{_, Arity} -> {M, DM, [{exported, FunName, Arity} | NM]};
_ -> Acc
end;
(_SyntaxTree, _CurrentFile, Acc) ->
Acc
end,
{[], [], []}, File),
Functions.
%available_functions(File, FunName, FunArity) ->
% Functions =fold_in_syntax_tree(fun
% ({function, {_, _}, FName, FnArity, _}, _CurrentFile, {M, DM, NM}) when FName =:= FunName, FnArity =:= FunArity ->
% {M, [{definition_match, FName, FunArity} | DM], NM};
% ({function, {_, _}, FName, Arity, _}, _CurrentFile, {M, DM, NM}) when FName =:= FunName ->
% {M, DM, [{definition, FName, Arity} | NM]};
% ({attribute, {_L, _C}, export, Exports}, _CurrentFile, {M, DM, NM}=Acc) ->
% case lists:keyfind(FunName, 1, Exports) of
% {_, Arity} when Arity =:= FunArity -> {[{exported_match, FunName, FunArity} | M], DM, NM};
% {_, Arity} -> {M, DM, [{exported, FunName, Arity} | NM]};
% _ -> Acc
% end;
% (_SyntaxTree, _CurrentFile, Acc) ->
% Acc
% end,
% {[], [], []}, File),
% Functions.

filter_unused_functions({_, []}) ->
[];
Expand Down
1 change: 1 addition & 0 deletions apps/erlangbridge/src/vscode_erlfmt_parse.yrl
Original file line number Diff line number Diff line change
Expand Up @@ -648,6 +648,7 @@ Erlang code.
form_info/0,
error_info/0
]).
-compile(nowarn_update_literal).

%% Start of Abstract Format

Expand Down
5 changes: 3 additions & 2 deletions lib/ErlangShellDebugger.ts
Original file line number Diff line number Diff line change
Expand Up @@ -247,11 +247,12 @@ export class ErlangShellForDebugging extends GenericShell {
}

/** compile specific files */
public Compile(startDir: string, args: string[]): Promise<number> {
public Compile(startDir: string, args: string[], erlPath: string): Promise<number> {
//if erl is used, -compile must be used
//var processArgs = ["-compile"].concat(args);
var processArgs = [].concat(args);
var result = this.RunProcess("erlc", startDir, processArgs);
const erlc = erlPath ? path.join(erlPath, 'erlc') : 'erlc';
var result = this.RunProcess(erlc, startDir, processArgs);
return result;
}

Expand Down
2 changes: 1 addition & 1 deletion lib/RebarRunner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -215,7 +215,7 @@ export class RebarRunner implements vscode.Disposable {
public async runScript(commands: string[]): Promise<string> {
const rootPath = getElangConfigConfiguration().rootPath;
const { output } = await new RebarShell(this.getRebarSearchPaths(), this.extensionPath, ErlangOutputAdapter(RebarRunner.RebarOutput))
.runScript(rootPath, commands);
.runScript(rootPath, commands, getElangConfigConfiguration().erlangPath);
return output;
}

Expand Down
44 changes: 36 additions & 8 deletions lib/RebarShell.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import * as fs from 'fs';
import * as fsPromises from 'node:fs/promises';
import * as path from 'path';
import { GenericShell, ILogOutput, IShellOutput } from './GenericShell';
import { getElangConfigConfiguration } from './ErlangConfigurationProvider';
Expand All @@ -20,8 +21,8 @@ export default class RebarShell extends GenericShell {
* @param cwd - The working directory where compilation will take place
* @returns Promise resolved or rejected when rebar exits
*/
public compile(cwd: string) : Promise<RebarShellResult> {
return this.runScript(cwd, ['compile']);
public compile(cwd: string, erlPath: string) : Promise<RebarShellResult> {
return this.runScript(cwd, ['compile'], erlPath);
}

/**
Expand All @@ -31,11 +32,14 @@ export default class RebarShell extends GenericShell {
* @param commands - Arguments to rebar
* @returns Promise resolved or rejected when rebar exits
*/
public async runScript(cwd: string, commands: string[]): Promise<RebarShellResult> {
public async runScript(cwd: string, commands: string[], erlPath: string): Promise<RebarShellResult> {
// Rebar may not have execution permission (e.g. if extension is built
// on Windows but installed on Linux). Let's always run rebar by escript.
let escript = (process.platform == 'win32' ? 'escript.exe' : 'escript');
let rebarFileName = this.getRebarFullPath();
if (erlPath !== '') {
escript = path.join(erlPath, escript);
}
let rebarFileName = await this.getRebarFullPath();
if (rebarFileName.search(' ') > -1) {
// There is at least one space in rebarPath. Use double quotes
// instead of single quotes for cross-operability between
Expand All @@ -60,12 +64,17 @@ export default class RebarShell extends GenericShell {
*
* @returns Full path to rebar executable
*/
private getRebarFullPath(): string {
private async getRebarFullPath(): Promise<string> {
const rebarSearchPaths = this.rebarSearchPaths.slice();
if (!rebarSearchPaths.includes(this.defaultRebarSearchPath)) {
rebarSearchPaths.push(this.defaultRebarSearchPath);
const onSearchPaths = this.findBestFile(rebarSearchPaths, ['rebar3', 'rebar'], '');
if (onSearchPaths !== '') {
return onSearchPaths;
}
const onPATH = await this.findExecutable('rebar3');
if (onPATH) {
return onPATH;
}
return this.findBestFile(rebarSearchPaths, ['rebar3', 'rebar'], 'rebar3');
return this.findBestFile([this.defaultRebarSearchPath], ['rebar3', 'rebar'], 'rebar3');
}

/**
Expand All @@ -92,6 +101,25 @@ export default class RebarShell extends GenericShell {
}
return result;
}

private async findExecutable(name: string): Promise<string | null> {
const envPath = process.env.PATH ?? '';
const dirs = envPath.split(path.delimiter);
const exts = process.platform === 'win32'
? (process.env.PATHEXT ?? '.EXE;.CMD;.BAT').split(';')
: [''];

for (const dir of dirs) {
for (const ext of exts) {
const full = path.join(dir, name + ext);
try {
await fsPromises.access(full, fsPromises.constants.X_OK);
return full;
} catch { /* keep looking */ }
}
}
return null;
}
}

export interface RebarShellResult {
Expand Down
10 changes: 6 additions & 4 deletions lib/erlangConnection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ export abstract class ErlangConnection extends EventEmitter {
protected events_receiver: http.Server;
_output: ILogOutput;
verbose: boolean;

erlPath: string;

public get isConnected(): boolean {
return this.erlangbridgePort > 0;
Expand All @@ -36,6 +36,7 @@ export abstract class ErlangConnection extends EventEmitter {
this._output = output;
this.erlangbridgePort = -1;
this.verbose = true;
this.erlPath = '';
}

protected log(msg: string): void {
Expand All @@ -62,8 +63,9 @@ export abstract class ErlangConnection extends EventEmitter {
}
}

public async Start(verbose: boolean): Promise<number> {
public async Start(verbose: boolean, erlPath: string): Promise<number> {
this.verbose = verbose;
this.erlPath = erlPath;
return new Promise<number>((a, r) => {
//this.debug("erlangConnection.Start");
this.compile_erlang_connection().then(() => {
Expand Down Expand Up @@ -91,11 +93,11 @@ export abstract class ErlangConnection extends EventEmitter {
//create dir if not exists
//compile erlang_connection in specifc diretory to avoid that the target can access to lspxxx.beam at debug time
if (!fs.existsSync(ebinDir)) {
fs.mkdirSync(ebinDir);
fs.mkdirSync(ebinDir, {recursive: true});
}

let args = ["-o", path.normalize(ebinDir)].concat(erlFiles);
return compiler.Compile(path.join(erlangBridgePath,'src'), args).then(res => {
return compiler.Compile(path.join(erlangBridgePath,'src'), args, this.erlPath).then(res => {
//this.debug("Compilation of erlang bridge...ok");
a(res);
}, exitCode => {
Expand Down
2 changes: 1 addition & 1 deletion lib/erlangDebugSession.ts
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,7 @@ export class ErlangDebugSession extends DebugSession implements ILogOutput {
this.log(`debugger launchRequest arguments : ${JSON.stringify(args)}`);
}
// Based on JS output path, not TS path
this.erlangConnection.Start(this._LaunchArguments.verbose).then(port => {
this.erlangConnection.Start(this._LaunchArguments.verbose, this._LaunchArguments.erlangPath).then(port => {
//this.debug("Local webserver for erlang is started");
this._port = port;
//Initialize the workflow only when webserver is started
Expand Down
2 changes: 1 addition & 1 deletion lib/lsp/lspclientextension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -184,7 +184,7 @@ function waitForSocket(options: any, callback: any, _tries: any) {
// TODO: convert to async function
function compileErlangBridge(extensionPath: string): Thenable<string> {
return new RebarShell([getElangConfigConfiguration().rebarPath], extensionPath, ErlangOutputAdapter())
.compile(extensionPath)
.compile(extensionPath, getElangConfigConfiguration().erlangPath)
.then(({ output }) => output);
// TODO: handle failure to compile erlangbridge
}
Expand Down
Loading
Loading