Skip to content

Commit 8a712d0

Browse files
committed
Add support for linting scg projects
1 parent d8d90f3 commit 8a712d0

10 files changed

Lines changed: 148 additions & 66 deletions

File tree

packages/extension/server/src/scgContextManager.ts

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
* Licensed under the MIT License. See LICENSE in the project root for license information.
44
*--------------------------------------------------------------------------------------------*/
55

6-
import { Connection, Emitter, Event } from "vscode-languageserver";
6+
import { Emitter, Event } from "vscode-languageserver";
77
import { DocumentProvider } from "./documentProvider";
88
import * as path from "path";
99
import { scgConfigFromYAML, ScgContext } from "@equinor/septic-config-lib";
@@ -14,8 +14,6 @@ export class ScgContextManager {
1414

1515
private contexts: Map<string, ScgContext> = new Map<string, ScgContext>();
1616

17-
private connection: Connection;
18-
1917
private cnfgProvider: SepticConfigProvider;
2018

2119
private _onDidUpdateContext: Emitter<string> = new Emitter<string>();
@@ -29,11 +27,9 @@ export class ScgContextManager {
2927
constructor(
3028
docProvider: DocumentProvider,
3129
cnfgProvider: SepticConfigProvider,
32-
connection: Connection,
3330
) {
3431
this.docProvider = docProvider;
3532
this.cnfgProvider = cnfgProvider;
36-
this.connection = connection;
3733
this.docProvider.onDidChangeDoc(async (uri) => {
3834
this.onDidChangeDoc(uri);
3935
});

packages/extension/server/src/server.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,6 @@ const langService: ILanguageService = createLanguageService(
6565
const scgContextManager = new ScgContextManager(
6666
documentProvider,
6767
langService.cnfgProvider,
68-
connection,
6968
);
7069

7170
const contextManager = new ContextManager(

packages/septic/src/cli/format.ts

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import * as fs from "fs";
99
import * as path from "path";
1010
import { SepticCnfg } from "../cnfg";
1111
import { SepticCnfgFormatter } from "../formatter";
12-
import { validateFileExists, createDocumentFromFile } from "./utils";
12+
import { createDocumentFromFile } from "../configProvider";
1313

1414
interface FormatOptions {
1515
file: string;
@@ -75,9 +75,7 @@ function checkFormatting(
7575
}
7676

7777
async function handler(options: FormatOptions): Promise<void> {
78-
validateFileExists(options.file);
79-
80-
const document = createDocumentFromFile(options.file);
78+
const document = await createDocumentFromFile(options.file);
8179
const originalContent = document.getText();
8280

8381
const edits = formatSepticConfig(document);

packages/septic/src/cli/lint.ts

Lines changed: 74 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -4,25 +4,29 @@
44
*--------------------------------------------------------------------------------------------*/
55

66
import yargs, { CommandModule } from "yargs";
7-
import { TextDocument } from "vscode-languageserver-textdocument";
87
import { SepticCnfg } from "../cnfg";
98
import {
109
getDiagnostics,
1110
SepticDiagnostic,
1211
SepticDiagnosticLevel,
1312
} from "../diagnostics";
1413
import { SepticMetaInfoProvider } from "../metaInfoProvider";
15-
import { validateFileExists, createDocumentFromFile } from "./utils";
14+
import {
15+
createDocumentFromFile,
16+
SepticConfigProviderFs,
17+
} from "../configProvider";
18+
import * as fs from "fs";
19+
import * as path from "path";
20+
import { scgConfigFromYAML, ScgContext } from "../scg";
1621

1722
interface LintOptions {
1823
file: string;
1924
ignore?: string[];
2025
septicversion?: string;
2126
}
2227

23-
async function lintSepticConfig(
24-
document: TextDocument,
25-
): Promise<SepticDiagnostic[]> {
28+
async function lintSepticConfig(filePath: string): Promise<SepticDiagnostic[]> {
29+
const document = await createDocumentFromFile(filePath);
2630
const cnfg = new SepticCnfg(document);
2731
cnfg.parse(undefined);
2832
await cnfg.updateObjectParents();
@@ -31,50 +35,97 @@ async function lintSepticConfig(
3135
);
3236
}
3337

38+
async function lintScg(scgConfigPath: string): Promise<SepticDiagnostic[]> {
39+
let scgConfig;
40+
try {
41+
const content = await fs.promises.readFile(scgConfigPath, {
42+
encoding: "utf-8",
43+
});
44+
scgConfig = scgConfigFromYAML(content);
45+
} catch (error) {
46+
console.error(`Failed to read or parse SCG config: ${error}`);
47+
process.exit(1);
48+
}
49+
const configProvider = new SepticConfigProviderFs();
50+
const scgContext = new ScgContext(
51+
"SCG Context",
52+
scgConfigPath,
53+
scgConfig,
54+
configProvider,
55+
);
56+
await scgContext.load();
57+
await scgContext.updateObjectParents();
58+
const diagnostics: SepticDiagnostic[] = [];
59+
for (const file of scgContext.files) {
60+
const cnfg = await configProvider.get(file);
61+
if (cnfg === undefined) {
62+
continue;
63+
}
64+
const templateDiagnostics = getDiagnostics(cnfg, scgContext).filter(
65+
(diagnostic) => diagnostic.level !== SepticDiagnosticLevel.hint,
66+
);
67+
diagnostics.push(...templateDiagnostics);
68+
}
69+
return diagnostics;
70+
}
71+
3472
function filterDiagnostics(
3573
diagnostics: SepticDiagnostic[],
3674
ignoreCodes?: string[],
3775
): SepticDiagnostic[] {
3876
if (!ignoreCodes || ignoreCodes.length === 0) {
3977
return diagnostics;
4078
}
41-
4279
const ignoreSet = new Set(ignoreCodes.map((code) => code.toUpperCase()));
4380
return diagnostics.filter(
4481
(diagnostic) => !ignoreSet.has(diagnostic.code.toUpperCase()),
4582
);
4683
}
4784

48-
function formatDiagnostic(diagnostic: SepticDiagnostic, uri: string): string {
85+
function formatDiagnostic(diagnostic: SepticDiagnostic): string {
4986
const line = diagnostic.range.start.line + 1;
5087
const character = diagnostic.range.start.character + 1;
5188
const level = diagnostic.level.toUpperCase();
5289
const code = diagnostic.code;
53-
return `${level} [${code}]: ${diagnostic.message} ${uri}#${line}:${character}`;
90+
return `${level} [${code}]: ${diagnostic.message} ${diagnostic.uri}#${line}:${character}`;
5491
}
5592

56-
function printDiagnostics(diagnostics: SepticDiagnostic[], uri: string): void {
93+
function printDiagnostics(diagnostics: SepticDiagnostic[]): void {
5794
if (diagnostics.length === 0) {
58-
console.log("No issues found.");
95+
console.log("No issues found!");
5996
return;
6097
}
61-
62-
console.log(`${diagnostics.length} issue(s) found:`);
63-
for (const diagnostic of diagnostics) {
64-
console.log(formatDiagnostic(diagnostic, uri));
98+
const files = diagnostics.map((diag) => diag.uri);
99+
const uniqueFiles = Array.from(new Set(files));
100+
for (const file of uniqueFiles) {
101+
const diagsForFile = diagnostics.filter((diag) => diag.uri === file);
102+
console.log(`\nLinting results for file: ${file}`);
103+
console.log(`${diagsForFile.length} issue(s) found:`);
104+
for (const diagnostic of diagsForFile) {
105+
console.log(formatDiagnostic(diagnostic));
106+
}
65107
}
66108
}
67109

68110
async function handler(options: LintOptions): Promise<void> {
69-
validateFileExists(options.file);
70-
71-
// Set the version to use for linting
72111
if (options.septicversion) {
73112
SepticMetaInfoProvider.setVersion(options.septicversion);
74113
}
114+
const allDiagnostics: SepticDiagnostic[] = [];
115+
if (options.file.endsWith(".cnfg")) {
116+
const fileDiagnostics = await lintSepticConfig(options.file);
117+
allDiagnostics.push(...fileDiagnostics);
118+
} else if (options.file.endsWith(".yaml")) {
119+
console.log(`Linting SCG project ${path.resolve(options.file)}`);
120+
const scgDiagnostics = await lintScg(options.file);
121+
allDiagnostics.push(...scgDiagnostics);
122+
} else {
123+
console.error(
124+
"Unsupported file type. Please provide a .cnfg or .yaml file.",
125+
);
126+
process.exit(1);
127+
}
75128

76-
const document = createDocumentFromFile(options.file);
77-
const allDiagnostics = await lintSepticConfig(document);
78129
const diagnostics = filterDiagnostics(allDiagnostics, options.ignore);
79130

80131
if (options.ignore && options.ignore.length > 0) {
@@ -86,7 +137,7 @@ async function handler(options: LintOptions): Promise<void> {
86137
}
87138
}
88139

89-
printDiagnostics(diagnostics, document.uri);
140+
printDiagnostics(diagnostics);
90141

91142
const exitCode = diagnostics.length === 0 ? 0 : 1;
92143
process.exit(exitCode);
@@ -102,7 +153,8 @@ export const lintCommand: CommandModule<object, LintOptions> = {
102153
return yargs
103154
.positional("file", {
104155
type: "string",
105-
description: "Path to Septic config file to lint",
156+
description:
157+
"Path to Septic config file or SCG config file to lint",
106158
demandOption: true,
107159
})
108160
.option("ignore", {
@@ -118,6 +170,7 @@ export const lintCommand: CommandModule<object, LintOptions> = {
118170
default: "latest",
119171
})
120172
.example("$0 lint config.cnfg", "Lint a config file")
173+
.example("$0 lint scg.yaml", "Lint all templates in an SCG project")
121174
.example(
122175
"$0 lint config.cnfg --ignore W101 W203",
123176
"Lint and ignore specific diagnostic codes",

packages/septic/src/cli/utils.ts

Lines changed: 0 additions & 25 deletions
This file was deleted.

packages/septic/src/cnfg.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,9 @@ export class SepticCnfg implements SepticContext, TextDocument {
6363
});
6464
}
6565

66-
public async parseAsync(token: CancellationToken): Promise<void> {
66+
public async parseAsync(
67+
token: CancellationToken | undefined,
68+
): Promise<void> {
6769
const scanner = new SepticScanner(this.doc.getText());
6870
const tokens = scanner.scanTokens();
6971
if (!tokens.tokens.length) {
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,38 @@
1+
import { TextDocument } from "vscode-languageserver-textdocument";
12
import { SepticCnfg } from "./cnfg";
3+
import * as path from "path";
4+
import * as fs from "fs";
25

36
export interface ISepticConfigProvider {
47
get(resource: string): Promise<SepticCnfg | undefined>;
58
}
9+
10+
export async function createDocumentFromFile(
11+
filePath: string,
12+
): Promise<TextDocument> {
13+
const fileContent = await fs.promises.readFile(filePath, {
14+
encoding: "utf-8",
15+
});
16+
return TextDocument.create(
17+
path.resolve(filePath),
18+
"septic",
19+
0,
20+
fileContent,
21+
);
22+
}
23+
24+
export class SepticConfigProviderFs implements ISepticConfigProvider {
25+
private cache: Map<string, SepticCnfg> = new Map<string, SepticCnfg>();
26+
constructor() {}
27+
28+
async get(resource: string): Promise<SepticCnfg | undefined> {
29+
if (this.cache.has(resource)) {
30+
return this.cache.get(resource);
31+
}
32+
const doc = await createDocumentFromFile(resource);
33+
const septicCnfg = new SepticCnfg(doc);
34+
await septicCnfg.parseAsync(undefined);
35+
this.cache.set(resource, septicCnfg);
36+
return septicCnfg;
37+
}
38+
}

packages/septic/src/diagnostics.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -121,19 +121,22 @@ export interface SepticDiagnostic {
121121
code: SepticDiagnosticCode;
122122
message: string;
123123
range: Range;
124+
uri?: string | undefined;
124125
}
125126

126127
function createDiagnostic(
127128
level: SepticDiagnosticLevel,
128129
range: Range,
129130
message: string,
130131
code: SepticDiagnosticCode,
132+
uri?: string,
131133
): SepticDiagnostic {
132134
return {
133135
level: level,
134136
range: range,
135137
message: message,
136138
code: code,
139+
uri: uri,
137140
};
138141
}
139142

@@ -152,7 +155,7 @@ export function validateStandAloneCalc(
152155
export function getDiagnostics(
153156
cnfg: SepticCnfg,
154157
contextProvider: SepticContext,
155-
) {
158+
): SepticDiagnostic[] {
156159
const diagnostics: SepticDiagnostic[] = [];
157160
diagnostics.push(...validateObjects(cnfg, contextProvider));
158161
diagnostics.push(...validateAlgs(cnfg, contextProvider));
@@ -173,7 +176,10 @@ export function getDiagnostics(
173176
}
174177
return true;
175178
});
176-
return filteredDiags;
179+
return filteredDiags.map((diag) => {
180+
diag.uri = cnfg.uri;
181+
return diag;
182+
});
177183
}
178184

179185
export function validateComments(cnfg: SepticCnfg): SepticDiagnostic[] {

packages/septic/src/index.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,3 +30,7 @@ export {
3030
} from "./diagnostics";
3131
export { compareCnfgs } from "./compare";
3232
export { SepticCnfgFormatter } from "./formatter";
33+
export {
34+
SepticConfigProviderFs,
35+
createDocumentFromFile,
36+
} from "./configProvider";

packages/septic/src/scg.ts

Lines changed: 23 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -85,16 +85,32 @@ export class ScgContext implements SepticContext {
8585

8686
private getFiles(scgConfig: ScgConfigSchema): string[] {
8787
return scgConfig.layout.map((layout) => {
88-
return (
89-
path.dirname(this.filePath) +
90-
"/" +
91-
scgConfig.templatepath +
92-
"/" +
93-
layout.name
94-
);
88+
if (this.filePath.startsWith("file:")) {
89+
return this.resolveUrlPath(scgConfig.templatepath, layout.name);
90+
}
91+
return this.resolveFilePath(scgConfig.templatepath, layout.name);
9592
});
9693
}
9794

95+
private resolveUrlPath(templatePath: string, layoutName: string): string {
96+
const baseUrl = new URL(this.filePath);
97+
const dirUrl = new URL(".", baseUrl);
98+
const relativePath = path.posix.join(templatePath, layoutName);
99+
const resolvedUrl = new URL(relativePath, dirUrl);
100+
return resolvedUrl.href;
101+
}
102+
103+
private resolveFilePath(templatePath: string, layoutName: string): string {
104+
const absoluteBasePath = path.isAbsolute(this.filePath)
105+
? this.filePath
106+
: path.resolve(this.filePath);
107+
return path.join(
108+
path.dirname(absoluteBasePath),
109+
templatePath,
110+
layoutName,
111+
);
112+
}
113+
98114
public async load(): Promise<void> {
99115
await Promise.all(
100116
this.files.map(async (file) => {

0 commit comments

Comments
 (0)