Format authored monorepo code with pinned language formatters
This commit is contained in:
+60
-113
@@ -13,13 +13,7 @@ import {
|
||||
type QuixosSourceBlockContext,
|
||||
type SourceBlockContext,
|
||||
} from "./generated/QuixosLockParser.js";
|
||||
import type {
|
||||
GitSource,
|
||||
LockedResource,
|
||||
QuixosLockDocument,
|
||||
QuixosRepositoryLock,
|
||||
QuixosSource,
|
||||
} from "./types.js";
|
||||
import type { GitSource, LockedResource, QuixosLockDocument, QuixosRepositoryLock, QuixosSource } from "./types.js";
|
||||
|
||||
export type QuixosLockDiagnostic = {
|
||||
phase: "syntax" | "validation" | "resolution";
|
||||
@@ -66,8 +60,7 @@ class SyntaxErrorListener extends BaseErrorListener {
|
||||
}
|
||||
}
|
||||
|
||||
const stringValue = (context: { getText(): string }): string =>
|
||||
JSON.parse(context.getText()) as string;
|
||||
const stringValue = (context: { getText(): string }): string => JSON.parse(context.getText()) as string;
|
||||
|
||||
const lowerSource = (context: SourceBlockContext): GitSource => ({
|
||||
resolver: "git",
|
||||
@@ -103,15 +96,16 @@ const issue = (
|
||||
path?: string,
|
||||
line = 1,
|
||||
column = 0,
|
||||
) => diagnostics.push({
|
||||
phase: "validation",
|
||||
code,
|
||||
message,
|
||||
fileName,
|
||||
line,
|
||||
column,
|
||||
path,
|
||||
});
|
||||
) =>
|
||||
diagnostics.push({
|
||||
phase: "validation",
|
||||
code,
|
||||
message,
|
||||
fileName,
|
||||
line,
|
||||
column,
|
||||
path,
|
||||
});
|
||||
|
||||
const validateImport = (
|
||||
importPath: string,
|
||||
@@ -123,11 +117,11 @@ const validateImport = (
|
||||
) => {
|
||||
const path = `imports[${index}]`;
|
||||
if (
|
||||
!importPath
|
||||
|| importPath.startsWith("/")
|
||||
|| importPath.includes("\\")
|
||||
|| importPath.split("/").some((segment) => !segment || segment === "." || segment === "..")
|
||||
|| /^[A-Za-z][A-Za-z0-9+.-]*:/.test(importPath)
|
||||
!importPath ||
|
||||
importPath.startsWith("/") ||
|
||||
importPath.includes("\\") ||
|
||||
importPath.split("/").some((segment) => !segment || segment === "." || segment === "..") ||
|
||||
/^[A-Za-z][A-Za-z0-9+.-]*:/.test(importPath)
|
||||
) {
|
||||
issue(
|
||||
diagnostics,
|
||||
@@ -141,12 +135,7 @@ const validateImport = (
|
||||
}
|
||||
};
|
||||
|
||||
const validateSource = (
|
||||
source: GitSource,
|
||||
path: string,
|
||||
fileName: string,
|
||||
diagnostics: QuixosLockDiagnostic[],
|
||||
) => {
|
||||
const validateSource = (source: GitSource, path: string, fileName: string, diagnostics: QuixosLockDiagnostic[]) => {
|
||||
if (!/^([0-9a-f]{40}|[0-9a-f]{64})$/.test(source.commit)) {
|
||||
issue(
|
||||
diagnostics,
|
||||
@@ -198,25 +187,22 @@ const validateSource = (
|
||||
}
|
||||
};
|
||||
|
||||
const validateQuixosSource = (
|
||||
source: QuixosSource,
|
||||
fileName: string,
|
||||
diagnostics: QuixosLockDiagnostic[],
|
||||
) => {
|
||||
const validateQuixosSource = (source: QuixosSource, fileName: string, diagnostics: QuixosLockDiagnostic[]) => {
|
||||
validateSource(source, "quixos", fileName, diagnostics);
|
||||
if (!source.policy) return;
|
||||
const forbiddenRefCharacters = new Set("~^:?*[\\");
|
||||
const invalidRef = !source.ref
|
||||
|| [...source.ref].some((character) => {
|
||||
const invalidRef =
|
||||
!source.ref ||
|
||||
[...source.ref].some((character) => {
|
||||
const code = character.charCodeAt(0);
|
||||
return code <= 0x20 || code === 0x7f || forbiddenRefCharacters.has(character);
|
||||
})
|
||||
|| source.ref.startsWith("/")
|
||||
|| source.ref.endsWith("/")
|
||||
|| source.ref.endsWith(".")
|
||||
|| source.ref.includes("..")
|
||||
|| source.ref.includes("@{")
|
||||
|| source.ref.includes("//");
|
||||
}) ||
|
||||
source.ref.startsWith("/") ||
|
||||
source.ref.endsWith("/") ||
|
||||
source.ref.endsWith(".") ||
|
||||
source.ref.includes("..") ||
|
||||
source.ref.includes("@{") ||
|
||||
source.ref.includes("//");
|
||||
if (invalidRef) {
|
||||
issue(
|
||||
diagnostics,
|
||||
@@ -247,10 +233,7 @@ const validateQuixosSource = (
|
||||
}
|
||||
};
|
||||
|
||||
export const parseQuixosLockDocument = (
|
||||
source: string,
|
||||
fileName = "<memory>",
|
||||
): QuixosLockDocumentParseResult => {
|
||||
export const parseQuixosLockDocument = (source: string, fileName = "<memory>"): QuixosLockDocumentParseResult => {
|
||||
const diagnostics: QuixosLockDiagnostic[] = [];
|
||||
const listener = new SyntaxErrorListener(fileName, diagnostics);
|
||||
const lexer = new QuixosLockLexer(CharStream.fromString(source));
|
||||
@@ -299,26 +282,13 @@ export const parseQuixosLockDocument = (
|
||||
|
||||
const imports = tree.importEntry().map((context, index) => {
|
||||
const importPath = stringValue(context.stringLiteral());
|
||||
validateImport(
|
||||
importPath,
|
||||
index,
|
||||
fileName,
|
||||
diagnostics,
|
||||
context.start?.line ?? 1,
|
||||
context.start?.column ?? 0,
|
||||
);
|
||||
validateImport(importPath, index, fileName, diagnostics, context.start?.line ?? 1, context.start?.column ?? 0);
|
||||
return importPath;
|
||||
});
|
||||
const repeatedImports = new Set<string>();
|
||||
imports.forEach((importPath, index) => {
|
||||
if (repeatedImports.has(importPath)) {
|
||||
issue(
|
||||
diagnostics,
|
||||
fileName,
|
||||
"duplicate-import",
|
||||
`Duplicate lock import ${importPath}`,
|
||||
`imports[${index}]`,
|
||||
);
|
||||
issue(diagnostics, fileName, "duplicate-import", `Duplicate lock import ${importPath}`, `imports[${index}]`);
|
||||
}
|
||||
repeatedImports.add(importPath);
|
||||
});
|
||||
@@ -355,36 +325,38 @@ export const parseQuixosLockDocument = (
|
||||
};
|
||||
};
|
||||
|
||||
export const parseQuixosLock = (
|
||||
source: string,
|
||||
fileName = "<memory>",
|
||||
): QuixosLockParseResult => {
|
||||
export const parseQuixosLock = (source: string, fileName = "<memory>"): QuixosLockParseResult => {
|
||||
const parsed = parseQuixosLockDocument(source, fileName);
|
||||
if (!parsed.ok) return parsed;
|
||||
if (parsed.document.kind === "fragment") {
|
||||
return {
|
||||
ok: false,
|
||||
diagnostics: [{
|
||||
phase: "validation",
|
||||
code: "expected-root-lock",
|
||||
message: "Expected a root Quixos lock, found a lock fragment",
|
||||
fileName,
|
||||
line: 1,
|
||||
column: 0,
|
||||
}],
|
||||
diagnostics: [
|
||||
{
|
||||
phase: "validation",
|
||||
code: "expected-root-lock",
|
||||
message: "Expected a root Quixos lock, found a lock fragment",
|
||||
fileName,
|
||||
line: 1,
|
||||
column: 0,
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
if (parsed.document.imports.length) {
|
||||
return {
|
||||
ok: false,
|
||||
diagnostics: [{
|
||||
phase: "resolution",
|
||||
code: "imports-require-file-resolution",
|
||||
message: "This lock has imports and must be loaded from its repository rather than parsed as an isolated string",
|
||||
fileName,
|
||||
line: 1,
|
||||
column: 0,
|
||||
}],
|
||||
diagnostics: [
|
||||
{
|
||||
phase: "resolution",
|
||||
code: "imports-require-file-resolution",
|
||||
message:
|
||||
"This lock has imports and must be loaded from its repository rather than parsed as an isolated string",
|
||||
fileName,
|
||||
line: 1,
|
||||
column: 0,
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
return {
|
||||
@@ -407,44 +379,23 @@ const sourceLines = (source: GitSource, indentation: string): string[] => [
|
||||
|
||||
const quixosSourceLines = (source: QuixosSource, indentation: string): string[] => [
|
||||
`${indentation}repository ${quoted(source.repository)};`,
|
||||
...(source.policy
|
||||
? [
|
||||
`${indentation}policy ${source.policy};`,
|
||||
`${indentation}ref ${quoted(source.ref)};`,
|
||||
]
|
||||
: []),
|
||||
...(source.policy ? [`${indentation}policy ${source.policy};`, `${indentation}ref ${quoted(source.ref)};`] : []),
|
||||
`${indentation}commit ${quoted(source.commit.toLowerCase())};`,
|
||||
];
|
||||
|
||||
export const formatQuixosLock = (lock: QuixosRepositoryLock): string => {
|
||||
const lines = [
|
||||
"quixos-lock version 1 {",
|
||||
" quixos source {",
|
||||
...quixosSourceLines(lock.quixos, " "),
|
||||
" }",
|
||||
];
|
||||
const lines = ["quixos-lock version 1 {", " quixos source {", ...quixosSourceLines(lock.quixos, " "), " }"];
|
||||
for (const resource of lock.resources) {
|
||||
lines.push(
|
||||
"",
|
||||
` ${resource.kind} ${resource.binding} source {`,
|
||||
...sourceLines(resource.source, " "),
|
||||
" }",
|
||||
);
|
||||
lines.push("", ` ${resource.kind} ${resource.binding} source {`, ...sourceLines(resource.source, " "), " }");
|
||||
}
|
||||
lines.push("}", "");
|
||||
return lines.join("\n");
|
||||
};
|
||||
|
||||
export const formatQuixosLockDocument = (document: QuixosLockDocument): string => {
|
||||
const lines = [
|
||||
`quixos-lock${document.kind === "fragment" ? " fragment" : ""} version 1 {`,
|
||||
];
|
||||
const lines = [`quixos-lock${document.kind === "fragment" ? " fragment" : ""} version 1 {`];
|
||||
if (document.kind === "root") {
|
||||
lines.push(
|
||||
" quixos source {",
|
||||
...quixosSourceLines(document.quixos, " "),
|
||||
" }",
|
||||
);
|
||||
lines.push(" quixos source {", ...quixosSourceLines(document.quixos, " "), " }");
|
||||
}
|
||||
for (const importPath of document.imports) {
|
||||
if (lines.length > 1) lines.push("");
|
||||
@@ -452,11 +403,7 @@ export const formatQuixosLockDocument = (document: QuixosLockDocument): string =
|
||||
}
|
||||
for (const resource of document.resources) {
|
||||
if (lines.length > 1) lines.push("");
|
||||
lines.push(
|
||||
` ${resource.kind} ${resource.binding} source {`,
|
||||
...sourceLines(resource.source, " "),
|
||||
" }",
|
||||
);
|
||||
lines.push(` ${resource.kind} ${resource.binding} source {`, ...sourceLines(resource.source, " "), " }");
|
||||
}
|
||||
lines.push("}", "");
|
||||
return lines.join("\n");
|
||||
|
||||
Reference in New Issue
Block a user