Skip to content

Commit b4af661

Browse files
committed
feat: Add LFortran as linter
see LFortran PR lfortran/lfortran#647 Fixes #589
1 parent 0525b75 commit b4af661

File tree

5 files changed

+109
-3
lines changed

5 files changed

+109
-3
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
99

1010
### Added
1111

12+
- Added linter support for [LFortran](https://lfortran.org/)
13+
([#589](https://github.com/fortran-lang/vscode-fortran-support/issues/589))
1214
- Added coverage reporting using `c8`
1315
([#613](https://github.com/fortran-lang/vscode-fortran-support/issues/613))
1416
- Added support for enhanced `gfotran` v11+ diagnostics using `-fdiagnostics-plain-output`

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -219,6 +219,7 @@
219219
"ifort",
220220
"ifx",
221221
"nagfor",
222+
"lfortran",
222223
"Disabled"
223224
],
224225
"markdownDescription": "Compiler used for linting support.",

src/features/linter-provider.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import * as semver from 'semver';
77
import * as vscode from 'vscode';
88

99
import { Logger } from '../services/logging';
10-
import { GNULinter, GNUModernLinter, IntelLinter, NAGLinter } from '../lib/linters';
10+
import { GNULinter, GNUModernLinter, IntelLinter, LFortranLinter, NAGLinter } from '../lib/linters';
1111
import {
1212
EXTENSION_ID,
1313
FortranDocumentSelector,
@@ -136,6 +136,7 @@ const GNU = new GNULinter();
136136
const GNU_NEW = new GNUModernLinter();
137137
const INTEL = new IntelLinter();
138138
const NAG = new NAGLinter();
139+
const LFORTRAN = new LFortranLinter();
139140

140141
export class FortranLintingProvider {
141142
constructor(private logger: Logger = new Logger()) {
@@ -261,6 +262,8 @@ export class FortranLintingProvider {
261262
return INTEL;
262263
case 'nagfor':
263264
return NAG;
265+
case 'lfortran':
266+
return LFORTRAN;
264267
default:
265268
return GNU;
266269
}

src/lib/linters.ts

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -234,3 +234,50 @@ export class NAGLinter extends Linter {
234234
return diagnostics;
235235
}
236236
}
237+
238+
export class LFortranLinter extends Linter {
239+
constructor() {
240+
super(
241+
'lfortran',
242+
/(?<fname>(?:\w:\\)?.*):(?<ls>\d+)-(?<le>\d+):(?<cs>\d+)-(?<ce>\d+): (?<sev>.+): (?<msg>.+)/g,
243+
{
244+
errors: [
245+
'C preprocessor error',
246+
'prescanner error',
247+
'tokenizer error',
248+
'syntax error',
249+
'semantic error',
250+
'ASR pass error',
251+
'code generation error',
252+
],
253+
warnings: ['warning'],
254+
infos: ['note'],
255+
hints: ['help', 'style suggestion'],
256+
},
257+
['--error-format=short'],
258+
[],
259+
'-J'
260+
);
261+
}
262+
263+
/**
264+
* <filename>:<line start>-<end>:<column start>-<end>: <severity>: <message>
265+
* @param msg linter results
266+
* @returns array of vscode.Diagnostic
267+
*/
268+
public parse(msg: string): vscode.Diagnostic[] {
269+
const matches = [...msg.matchAll(this.regex)];
270+
const diagnostics: vscode.Diagnostic[] = [];
271+
for (const m of matches) {
272+
const g = m.groups;
273+
const [_, name, ls, le, cs, ce, msg_type, msg] = m;
274+
const range = new vscode.Range(
275+
new vscode.Position(parseInt(ls) - 1, parseInt(cs) - 1),
276+
new vscode.Position(parseInt(le) - 1, parseInt(ce))
277+
);
278+
const severity = this.getSeverityLevel(msg_type.toLowerCase());
279+
diagnostics.push(new vscode.Diagnostic(range, msg, severity));
280+
}
281+
return diagnostics;
282+
}
283+
}

test/linter-provider.test.ts

Lines changed: 55 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,13 @@ import {
1313
import * as fg from 'fast-glob';
1414

1515
import { FortranLintingProvider } from '../src/features/linter-provider';
16-
import { GNULinter, GNUModernLinter, IntelLinter, NAGLinter } from '../src/lib/linters';
16+
import {
17+
GNULinter,
18+
GNUModernLinter,
19+
IntelLinter,
20+
NAGLinter,
21+
LFortranLinter,
22+
} from '../src/lib/linters';
1723
import { EXTENSION_ID, pipInstall } from '../src/lib/tools';
1824

1925
suite('Linter integration', () => {
@@ -75,7 +81,7 @@ suite('Linter integration', () => {
7581
});
7682

7783
test('Linter user setting returns the right linter internally', () => {
78-
const names = ['gfortran', 'ifort', 'ifx', 'nagfor', 'fake'];
84+
const names = ['gfortran', 'ifort', 'ifx', 'nagfor', 'lfortran', 'fake'];
7985
for (const n of names) {
8086
const compiler = linter['getLinter'](n);
8187
if (n === 'gfortran') {
@@ -88,6 +94,8 @@ suite('Linter integration', () => {
8894
strictEqual(compiler instanceof IntelLinter, true);
8995
} else if (n === 'nagfor') {
9096
strictEqual(compiler instanceof NAGLinter, true);
97+
} else if (n == 'lfortran') {
98+
strictEqual(compiler instanceof LFortranLinter, true);
9199
} else {
92100
strictEqual(compiler instanceof GNULinter, true);
93101
}
@@ -787,3 +795,48 @@ Sequence Error: lint/err-mod.f90, line 3: The IMPLICIT statement cannot occur he
787795
deepStrictEqual(diags, ref);
788796
});
789797
});
798+
799+
// -----------------------------------------------------------------------------
800+
801+
suite('LFortran (lfortran) lint single', () => {
802+
const linter = new LFortranLinter();
803+
const msg = `
804+
lint/err-mod.f90:3-3:5-12: syntax error: Token 'implicit' is unexpected here
805+
`;
806+
suite('REGEX matches', () => {
807+
const matches = [...msg.matchAll(linter.regex)];
808+
const g = matches[0].groups;
809+
test('REGEX: filename', () => {
810+
strictEqual(g?.['fname'], 'lint/err-mod.f90');
811+
});
812+
test('REGEX: line number start <ls>', () => {
813+
strictEqual(g?.['ls'], '3');
814+
});
815+
test('REGEX: line number end <le>', () => {
816+
strictEqual(g?.['le'], '3');
817+
});
818+
test('REGEX: column number start <cs>', () => {
819+
strictEqual(g?.['cs'], '5');
820+
});
821+
test('REGEX: column number end <ce>', () => {
822+
strictEqual(g?.['ce'], '12');
823+
});
824+
test('REGEX: severity <sev>', () => {
825+
strictEqual(g?.['sev'], 'syntax error');
826+
});
827+
test('REGEX: message <msg>', () => {
828+
strictEqual(g?.['msg'], `Token 'implicit' is unexpected here`);
829+
});
830+
});
831+
test('Diagnostics Array', () => {
832+
const diags = linter.parse(msg);
833+
const ref = [
834+
new Diagnostic(
835+
new Range(new Position(2, 4), new Position(2, 12)),
836+
`Token 'implicit' is unexpected here`,
837+
DiagnosticSeverity.Error
838+
),
839+
];
840+
deepStrictEqual(diags, ref);
841+
});
842+
});

0 commit comments

Comments
 (0)