Skip to content

Commit 9d78b4f

Browse files
committed
feat: Gfortran v11+enhanced linting support
Fixes #523 Adds the option to detect the gfortran version and deploy the more advanced diagnostic option -fdiagnostic-plain-output instead of parsing the actual compiler messages which can be problematic due to the various edge cases
1 parent 78dbd7d commit 9d78b4f

File tree

4 files changed

+118
-1
lines changed

4 files changed

+118
-1
lines changed

package-lock.json

Lines changed: 14 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -616,6 +616,7 @@
616616
"@types/glob": "^7.2.0",
617617
"@types/mocha": "^9.1.0",
618618
"@types/node": "^16.11.39",
619+
"@types/semver": "^7.3.12",
619620
"@types/vscode": "^1.63.0",
620621
"@types/which": "^2.0.1",
621622
"@typescript-eslint/eslint-plugin": "^5.33.1",
@@ -645,6 +646,7 @@
645646
"dependencies": {
646647
"fast-glob": "^3.2.11",
647648
"glob": "^8.0.3",
649+
"semver": "^7.3.7",
648650
"vscode-languageclient": "^8.0.2",
649651
"which": "^2.0.2"
650652
}

src/features/linter-provider.ts

Lines changed: 98 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,9 @@
33
import * as path from 'path';
44
import * as cp from 'child_process';
55
import which from 'which';
6-
6+
import * as semver from 'semver';
77
import * as vscode from 'vscode';
8+
89
import { Logger } from '../services/logging';
910
import {
1011
EXTENSION_ID,
@@ -19,10 +20,13 @@ import { RescanLint } from './commands';
1920
import { GlobPaths } from '../lib/glob-paths';
2021

2122
export class LinterSettings {
23+
private _modernGNU: boolean;
24+
private _version: string;
2225
private config: vscode.WorkspaceConfiguration;
2326

2427
constructor(private logger: Logger = new Logger()) {
2528
this.config = vscode.workspace.getConfiguration(EXTENSION_ID);
29+
this.GNUVersion(this.compiler); // populates version & modernGNU
2630
}
2731
public update(event: vscode.ConfigurationChangeEvent) {
2832
console.log('update settings');
@@ -50,6 +54,49 @@ export class LinterSettings {
5054
public get modOutput(): string {
5155
return this.config.get<string>('linter.modOutput');
5256
}
57+
58+
// END OF API SETTINGS
59+
60+
/**
61+
* Returns the version of the compiler and populates the internal variables
62+
* `modernGNU` and `version`.
63+
* @note Only supports `gfortran`
64+
*/
65+
private GNUVersion(compiler: string): string | undefined {
66+
// Only needed for gfortran's diagnostics flag
67+
this.modernGNU = false;
68+
if (compiler !== 'gfortran') return;
69+
const child = cp.spawnSync(compiler, ['--version']);
70+
if (child.error || child.status !== 0) {
71+
this.logger.error(`[lint] Could not spawn ${compiler} to check version.`);
72+
return;
73+
}
74+
const regex = /^GNU Fortran \([\w.-]+\) (?<version>.*)$/gm;
75+
const version = regex.exec(child.stdout.toString().trim()).groups['version'];
76+
if (semver.valid(version)) {
77+
this.version = version;
78+
this.logger.info(`[lint] Found GNU Fortran version ${version}`);
79+
this.logger.debug(`[lint] Using Modern GNU Fortran diagnostics: ${this.modernGNU}`);
80+
return version;
81+
} else {
82+
this.logger.error(`[lint] invalid compiler version: ${version}`);
83+
}
84+
}
85+
86+
public get version(): string {
87+
return this._version;
88+
}
89+
private set version(version: string) {
90+
this._version = version;
91+
this.modernGNU = semver.gte(version, '11.0.0');
92+
}
93+
public get modernGNU(): boolean {
94+
return this._modernGNU;
95+
}
96+
private set modernGNU(modernGNU: boolean) {
97+
this._modernGNU = modernGNU;
98+
}
99+
53100
// FYPP options
54101

55102
public get fyppEnabled(): boolean {
@@ -365,6 +412,7 @@ export class FortranLintingProvider {
365412
const matches = [...msg.matchAll(regex)];
366413
switch (this.compiler) {
367414
case 'gfortran':
415+
if (this.settings.modernGNU) return this.linterParserGCCPlainText(matches);
368416
return this.linterParserGCC(matches);
369417

370418
case 'ifx':
@@ -419,6 +467,42 @@ export class FortranLintingProvider {
419467
return diagnostics;
420468
}
421469

470+
private linterParserGCCPlainText(matches: RegExpMatchArray[]): vscode.Diagnostic[] {
471+
const diagnostics: vscode.Diagnostic[] = [];
472+
for (const m of matches) {
473+
const g = m.groups;
474+
// m[0] is the entire match and then the captured groups follow
475+
const lineNo: number = parseInt(g['ln']);
476+
const colNo: number = parseInt(g['cn']);
477+
const msgSev: string = g['sev'];
478+
const msg: string = g['msg'];
479+
480+
const range = new vscode.Range(
481+
new vscode.Position(lineNo - 1, colNo),
482+
new vscode.Position(lineNo - 1, colNo)
483+
);
484+
485+
let severity: vscode.DiagnosticSeverity;
486+
switch (msgSev.toLowerCase()) {
487+
case 'error':
488+
case 'fatal error':
489+
severity = vscode.DiagnosticSeverity.Error;
490+
break;
491+
case 'warning':
492+
severity = vscode.DiagnosticSeverity.Warning;
493+
break;
494+
case 'info': // gfortran does not produce info AFAIK
495+
severity = vscode.DiagnosticSeverity.Information;
496+
break;
497+
default:
498+
severity = vscode.DiagnosticSeverity.Error;
499+
break;
500+
}
501+
diagnostics.push(new vscode.Diagnostic(range, msg, severity));
502+
}
503+
return diagnostics;
504+
}
505+
422506
private linterParserIntel(matches: RegExpMatchArray[]): vscode.Diagnostic[] {
423507
const diagnostics: vscode.Diagnostic[] = [];
424508
for (const m of matches) {
@@ -529,6 +613,16 @@ export class FortranLintingProvider {
529613
-------------------------------------------------------------------------
530614
*/
531615
case 'gfortran':
616+
/**
617+
-----------------------------------------------------------------------
618+
COMPILER: MESSAGE ANATOMY:
619+
file:line:column: Severity: msg
620+
-----------------------------------------------------------------------
621+
see https://regex101.com/r/73TZQn/1
622+
*/
623+
if (this.settings.modernGNU) {
624+
return /(?<fname>(?:\w:\\)?.*):(?<ln>\d+):(?<cn>\d+): (?<sev>Error|Warning|Fatal Error): (?<msg>.*)/g;
625+
}
532626
// see https://regex101.com/r/hZtk3f/1
533627
return /(?:^(?<fname>(?:\w:\\)?.*):(?<ln>\d+):(?<cn>\d+):(?:\s+.*\s+.*?\s+)(?<sev1>Error|Warning|Fatal Error):\s(?<msg1>.*)$)|(?:^(?<bin>\w+):\s*(?<sev2>\w+\s*\w*):\s*(?<msg2>.*)$)/gm;
534628

@@ -574,6 +668,9 @@ export class FortranLintingProvider {
574668
switch (compiler) {
575669
case 'flang':
576670
case 'gfortran':
671+
if (this.settings.modernGNU) {
672+
return ['-fsyntax-only', '-cpp', '-fdiagnostics-plain-output'];
673+
}
577674
return ['-fsyntax-only', '-cpp', '-fdiagnostics-show-option'];
578675

579676
// ifort theoretically supports fsyntax-only too but I had trouble

test/linter-provider.test.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,7 @@ suite('fypp Linter integration', () => {
122122
suite('GNU (gfortran) lint single', () => {
123123
const linter = new FortranLintingProvider();
124124
linter['compiler'] = 'gfortran';
125+
linter['settings']['modernGNU'] = false;
125126
const msg = `
126127
C:\\Some\\random\\path\\sample.f90:4:18:
127128
@@ -164,6 +165,7 @@ Error: Missing actual argument for argument ‘a’ at (1)
164165
suite('GNU (gfortran) lint multiple', () => {
165166
const linter = new FortranLintingProvider();
166167
linter['compiler'] = 'gfortran';
168+
linter['settings']['modernGNU'] = false;
167169
const msg = `
168170
/fetch/main/FETCH.F90:1629:24:
169171
@@ -236,6 +238,7 @@ f951: some warnings being treated as errors
236238
suite('GNU (gfortran) lint preprocessor', () => {
237239
const linter = new FortranLintingProvider();
238240
linter['compiler'] = 'gfortran';
241+
linter['settings']['modernGNU'] = false;
239242
const msg = `
240243
gfortran: fatal error: cannot execute '/usr/lib/gcc/x86_64-linux-gnu/9/f951': execv: Argument list too long\ncompilation terminated.
241244
`;
@@ -277,6 +280,7 @@ gfortran: fatal error: cannot execute '/usr/lib/gcc/x86_64-linux-gnu/9/f951': ex
277280
suite('GNU (gfortran) lint preprocessor multiple', () => {
278281
const linter = new FortranLintingProvider();
279282
linter['compiler'] = 'gfortran';
283+
linter['settings']['modernGNU'] = false;
280284
const msg = `
281285
f951: Warning: Nonexistent include directory '/Code/TypeScript/vscode-fortran-support/test/fortran/include' [-Wmissing-include-dirs]
282286
/Code/TypeScript/vscode-fortran-support/test/fortran/sample.f90:4:18:

0 commit comments

Comments
 (0)