feat: Phase 1 — extract the client (web SPA + desktop) to dtoro/oikos-web
Some checks failed
ci / build-test (push) Has been cancelled
ci / docker-build (push) Has been cancelled

Problem: the hexagonal refactor churns the backend tree for nine more
phases; the UI delivery stack (web/ SPA, cmd/desktop Wails wrapper,
compose/web image) must move to its own repo first so doc/layout
rewrites land once on a backend-only tree.

Change:
- New repo git.hubris.network/dtoro/oikos-web (v0.33.0): web/, desktop/
  (updateURL repointed to oikos-web releases), compose/, own CI (web +
  desktop jobs), own deploy script (CI-green gate, TOCTOU guard,
  version-tagged images, prune-to-3), own webhook receiver on :9798 +
  launchd unit, own compose project publishing the same 8091:80.
- Cutover executed on mac-mini in order: oikos stack's web service
  stopped+removed, oikos-web project brought up on 8091; outer Caddy
  untouched (targets the published port) — serving + Authentik flow +
  /wails 404 quirk verified post-cutover.
- Stripped from oikos: web/, cmd/desktop/, compose/web/, desktop CI
  workflow, ci.yml web job, Makefile ui/desktop/desktop-package/install
  targets, the compose web service, oikos-web from deploy.sh's fallback
  prune list; wails + go-keyring dropped from go.mod, vendor synced.
- README / CONTRIBUTING / AGENTS.md / .agents dev+operations docs now
  point at the new repo; mbse + mascot design docs carry a path note.

Risk: production SPA serving depends on the new pipeline now; rollback
is versioned-image re-up of the old web service from a pre-split
checkout (port 8091). Desktop builds installed before the split still
check dtoro/oikos releases — one manual reinstall, noted in the
oikos-web release notes.

Verification: go vet, make test (race), make generate-check, golangci
(no new findings; baseline down 400→365); post-cutover curls —
localhost:8091 200, /wails/runtime.js 404, outer Caddy 302 Authentik.
This commit is contained in:
2026-08-15 22:27:52 +02:00
parent e074f04bdf
commit d4d99a7473
18854 changed files with 2615729 additions and 173735 deletions

View File

@@ -0,0 +1,21 @@
import type { AST } from 'svelte-eslint-parser';
import type { Linter } from 'eslint';
type Define = {
loc: AST.Position;
};
export declare class CommentDirectives {
private readonly reportUnusedDisableDirectives;
private readonly ruleId;
private readonly lineDisableDirectives;
private readonly blockDirectives;
constructor(options: {
reportUnusedDisableDirectives?: boolean;
ruleId: string;
});
filterMessages(messages: Linter.LintMessage[]): Linter.LintMessage[];
disableLine(line: number, rule: string | ((ruleId: string) => boolean), define: Define): void;
disableBlock(pos: AST.Position, rule: string | ((ruleId: string) => boolean), define: Define): void;
enableBlock(pos: AST.Position, rule: string | ((ruleId: string) => boolean), define: Define): void;
private block;
}
export {};

View File

@@ -0,0 +1,162 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.CommentDirectives = void 0;
const COMPUTED = Symbol();
const ALL = Symbol();
class CommentDirectives {
constructor(options) {
this.lineDisableDirectives = new Map();
this.blockDirectives = new Map();
this.ruleId = options.ruleId;
this.reportUnusedDisableDirectives = Boolean(options?.reportUnusedDisableDirectives);
}
filterMessages(messages) {
const { lineDisableDirectives, blockDirectives, reportUnusedDisableDirectives } = this;
const usedDirectives = new Set();
if (reportUnusedDisableDirectives) {
const allBlocks = [];
for (const bs of blockDirectives.values()) {
for (const b of bs) {
allBlocks.push(b);
}
}
const blockEnableDirectives = new Set();
for (const block of allBlocks.sort((a, b) => comparePos(b.loc, a.loc))) {
if (block.kind === 'enable') {
if (block.targetRule === ALL) {
blockEnableDirectives.clear();
}
blockEnableDirectives.add(block);
}
else if (block.kind === 'disable') {
if (block.targetRule === ALL) {
for (const b of blockEnableDirectives) {
usedDirectives.add(b);
}
blockEnableDirectives.clear();
}
else {
for (const b of [...blockEnableDirectives]) {
if (block.targetRule === b.targetRule || b.targetRule === ALL) {
usedDirectives.add(b);
blockEnableDirectives.delete(b);
}
}
}
}
}
}
let filteredMessages = messages.filter(isEnable);
if (reportUnusedDisableDirectives) {
const usedDirectiveKeys = new Set([...usedDirectives].map((d) => locToKey(d.define.loc)));
filteredMessages = filteredMessages.filter((m) => {
if (m.ruleId !== this.ruleId) {
return true;
}
if (usedDirectiveKeys.has(messageToKey(m))) {
return false;
}
return true;
});
}
return filteredMessages;
/** Checks wether given rule is enable */
function isEnable(message) {
if (!message.ruleId) {
// Maybe fatal error
return true;
}
for (const disableLines of getFromRule(lineDisableDirectives, message.ruleId)) {
for (const disableLine of disableLines.get(message.line) ?? []) {
if (!disableLine.rule(message.ruleId)) {
continue;
}
usedDirectives.add(disableLine);
return false;
}
}
const blocks = getFromRule(blockDirectives, message.ruleId)
.reduce((p, c) => p.concat(c), [])
.sort((a, b) => comparePos(b.loc, a.loc));
for (const block of blocks) {
if (comparePos(message, block.loc) < 0) {
continue;
}
if (!block.rule(message.ruleId)) {
continue;
}
if (block.kind === 'enable') {
return true;
}
// block.kind === "disable"
usedDirectives.add(block);
return false;
}
return true;
}
/** Compare locations */
function comparePos(a, b) {
return a.line - b.line || a.column - b.column;
}
}
disableLine(line, rule, define) {
const key = typeof rule === 'string' ? rule : COMPUTED;
let disableLines = this.lineDisableDirectives.get(key);
if (!disableLines) {
disableLines = new Map();
this.lineDisableDirectives.set(key, disableLines);
}
let disableLine = disableLines.get(line);
if (!disableLine) {
disableLine = [];
disableLines.set(line, disableLine);
}
const disable = {
line,
rule: typeof rule === 'string' ? (id) => id === rule : rule,
define
};
disableLine.push(disable);
}
disableBlock(pos, rule, define) {
this.block(pos, rule, define, 'disable');
}
enableBlock(pos, rule, define) {
this.block(pos, rule, define, 'enable');
}
block(pos, rule, define, kind) {
const key = typeof rule === 'string' ? rule : COMPUTED;
let blocks = this.blockDirectives.get(key);
if (!blocks) {
blocks = [];
this.blockDirectives.set(key, blocks);
}
const disable = {
loc: pos,
rule: typeof rule === 'string' ? (id) => id === rule : rule,
kind,
targetRule: typeof rule === 'string' ? rule : ALL,
define
};
blocks.push(disable);
}
}
exports.CommentDirectives = CommentDirectives;
/** Get the list of directives from given rule */
function getFromRule(map, ruleId) {
return [map.get(ruleId), map.get(COMPUTED)].filter((a) => Boolean(a));
}
/**
* Gets the key of location
*/
function locToKey(location) {
return `line:${location.line},column${location.column}`;
}
/**
* Gets the key of message location
*/
function messageToKey(message) {
return `line:${message.line},column${
// -1 because +1 by ESLint's `report-translator`.
message.column - 1}`;
}

View File

@@ -0,0 +1,11 @@
import { CommentDirectives } from './comment-directives';
export declare class Shared {
readonly commentDirectives: CommentDirectives[];
newCommentDirectives(options: ConstructorParameters<typeof CommentDirectives>[0]): CommentDirectives;
}
/** Start sharing and make the data available. */
export declare function beginShared(filename: string): void;
/** Get the shared data and end the sharing. */
export declare function terminateShared(filename: string): Shared | null;
/** If sharing has started, get the shared data. */
export declare function getShared(filename: string): Shared | null;

View File

@@ -0,0 +1,33 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.Shared = void 0;
exports.beginShared = beginShared;
exports.terminateShared = terminateShared;
exports.getShared = getShared;
const comment_directives_1 = require("./comment-directives");
class Shared {
constructor() {
this.commentDirectives = [];
}
newCommentDirectives(options) {
const directives = new comment_directives_1.CommentDirectives(options);
this.commentDirectives.push(directives);
return directives;
}
}
exports.Shared = Shared;
const sharedMap = new Map();
/** Start sharing and make the data available. */
function beginShared(filename) {
sharedMap.set(filename, new Shared());
}
/** Get the shared data and end the sharing. */
function terminateShared(filename) {
const result = sharedMap.get(filename);
sharedMap.delete(filename);
return result ?? null;
}
/** If sharing has started, get the shared data. */
function getShared(filename) {
return sharedMap.get(filename) ?? null;
}

View File

@@ -0,0 +1,5 @@
import type { AST } from 'svelte-eslint-parser';
import type { RuleContext } from '../../types';
import type { ASTNodeWithParent } from '../../types-for-node';
/** Extract comments */
export declare function extractLeadingComments(context: RuleContext, node: ASTNodeWithParent): (AST.Token | AST.Comment)[];

View File

@@ -0,0 +1,32 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.extractLeadingComments = extractLeadingComments;
const eslint_utils_1 = require("@eslint-community/eslint-utils");
const compat_1 = require("../../utils/compat");
/** Extract comments */
function extractLeadingComments(context, node) {
const sourceCode = (0, compat_1.getSourceCode)(context);
const beforeToken = sourceCode.getTokenBefore(node, {
includeComments: false,
filter(token) {
if ((0, eslint_utils_1.isOpeningParenToken)(token)) {
return false;
}
const astToken = token;
if (astToken.type === 'HTMLText') {
return false;
}
return astToken.type !== 'HTMLComment';
}
});
if (beforeToken) {
return sourceCode
.getTokensBetween(beforeToken, node, { includeComments: true })
.filter(isComment);
}
return sourceCode.getTokensBefore(node, { includeComments: true }).filter(isComment);
}
/** Checks whether given token is comment token */
function isComment(token) {
return token.type === 'HTMLComment' || token.type === 'Block' || token.type === 'Line';
}

View File

@@ -0,0 +1,15 @@
import type { AST } from 'svelte-eslint-parser';
import type { RuleContext } from '../../types';
export type IgnoreItemWithoutCode = {
range: [number, number];
code: null;
token: AST.Token | AST.Comment;
};
export type IgnoreItem = {
range: [number, number];
code: string;
codeForV5: string;
token: AST.Token | AST.Comment;
};
/** Extract all svelte-ignore comment items */
export declare function getSvelteIgnoreItems(context: RuleContext): (IgnoreItem | IgnoreItemWithoutCode)[];

View File

@@ -0,0 +1,94 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.getSvelteIgnoreItems = getSvelteIgnoreItems;
const compat_1 = require("../../utils/compat");
const SVELTE_IGNORE_PATTERN = /^\s*svelte-ignore/m;
/**
* Map of legacy code -> new code
* See https://github.com/sveltejs/svelte/blob/c9202a889612df3c2fcb369096a5573668be99d6/packages/svelte/src/compiler/utils/extract_svelte_ignore.js#L6
*/
const V5_REPLACEMENTS = {
'non-top-level-reactive-declaration': 'reactive_declaration_invalid_placement',
'module-script-reactive-declaration': 'reactive_declaration_module_script',
'empty-block': 'block_empty',
'avoid-is': 'attribute_avoid_is',
'invalid-html-attribute': 'attribute_invalid_property_name',
'a11y-structure': 'a11y_figcaption_parent',
'illegal-attribute-character': 'attribute_illegal_colon',
'invalid-rest-eachblock-binding': 'bind_invalid_each_rest',
'unused-export-let': 'export_let_unused'
};
/** Extract all svelte-ignore comment items */
function getSvelteIgnoreItems(context) {
const sourceCode = (0, compat_1.getSourceCode)(context);
const ignoreComments = [];
for (const comment of sourceCode.getAllComments()) {
const ignores = extractSvelteIgnore(comment.value, comment.range[0] + 2, comment);
if (ignores) {
ignoreComments.push(...ignores);
}
else if (hasMissingCodeIgnore(comment.value)) {
ignoreComments.push({
range: comment.range,
code: null,
token: comment
});
}
}
for (const token of sourceCode.ast.tokens) {
if (token.type === 'HTMLComment') {
const text = token.value.slice(4, -3);
const ignores = extractSvelteIgnore(text, token.range[0] + 4, token);
if (ignores) {
ignoreComments.push(...ignores);
}
else if (hasMissingCodeIgnore(text)) {
ignoreComments.push({
range: token.range,
code: null,
token
});
}
}
}
ignoreComments.sort((a, b) => b.range[0] - a.range[0]);
return ignoreComments;
}
/** Extract svelte-ignore rule names */
function extractSvelteIgnore(text, startIndex, token) {
const m1 = SVELTE_IGNORE_PATTERN.exec(text);
if (!m1) {
return null;
}
const ignoreStart = m1.index + m1[0].length;
const beforeText = text.slice(ignoreStart);
if (!/^\s/.test(beforeText) || !beforeText.trim()) {
return null;
}
let start = startIndex + ignoreStart;
const results = [];
for (const code of beforeText.split(/\s/)) {
const end = start + code.length;
const trimmed = code.trim();
if (trimmed) {
results.push({
code: trimmed,
codeForV5: V5_REPLACEMENTS[trimmed] || trimmed.replace(/-/gu, '_'),
range: [start, end],
token
});
}
start = end + 1; /* space */
}
return results;
}
/** Checks whether given comment has missing code svelte-ignore */
function hasMissingCodeIgnore(text) {
const m1 = SVELTE_IGNORE_PATTERN.exec(text);
if (!m1) {
return false;
}
const ignoreStart = m1.index + m1[0].length;
const beforeText = text.slice(ignoreStart);
return !beforeText.trim();
}

View File

@@ -0,0 +1,32 @@
import type { AST } from 'svelte-eslint-parser';
import type { RuleContext } from '../../types';
import type { IgnoreItem } from './ignore-comment';
export type SvelteCompileWarnings = {
warnings: Warning[];
unusedIgnores: IgnoreItem[];
kind: 'warn' | 'error';
stripStyleElements: AST.SvelteStyleElement[];
};
export type Loc = {
start?: {
line: number;
column: number;
character: number;
};
end?: {
line: number;
column: number;
character: number;
};
};
export type Warning = ({
code: string;
message: string;
} | {
code?: undefined;
message: string;
}) & Loc;
/**
* Get svelte compile warnings
*/
export declare function getSvelteCompileWarnings(context: RuleContext): SvelteCompileWarnings;

View File

@@ -0,0 +1,558 @@
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.getSvelteCompileWarnings = getSvelteCompileWarnings;
const compiler = __importStar(require("svelte/compiler"));
const sourcemap_codec_1 = require("@jridgewell/sourcemap-codec");
const lines_and_columns_1 = require("../../utils/lines-and-columns");
const typescript_1 = require("./transform/typescript");
const babel_1 = require("./transform/babel");
const postcss_1 = require("./transform/postcss");
const sass_1 = require("./transform/sass");
const less_1 = require("./transform/less");
const stylus_1 = require("./transform/stylus");
const ignore_comment_1 = require("./ignore-comment");
const extract_leading_comments_1 = require("./extract-leading-comments");
const ast_utils_1 = require("../../utils/ast-utils");
const path_1 = __importDefault(require("path"));
const fs_1 = __importDefault(require("fs"));
const semver_1 = __importDefault(require("semver"));
const compat_1 = require("../../utils/compat");
const STYLE_TRANSFORMS = {
postcss: postcss_1.transform,
pcss: postcss_1.transform,
scss: (node, text, context) => (0, sass_1.transform)(node, text, context, 'scss'),
sass: (node, text, context) => (0, sass_1.transform)(node, text, context, 'sass'),
less: less_1.transform,
stylus: stylus_1.transform,
styl: stylus_1.transform
};
const CSS_WARN_CODES = new Set([
'css-unused-selector',
'css_unused_selector',
'css-invalid-global',
'css-invalid-global-selector'
]);
const cacheAll = new WeakMap();
/**
* Get svelte compile warnings
*/
function getSvelteCompileWarnings(context) {
const sourceCode = (0, compat_1.getSourceCode)(context);
const cache = cacheAll.get(sourceCode.ast);
if (cache) {
return cache;
}
const result = getSvelteCompileWarningsWithoutCache(context);
cacheAll.set(sourceCode.ast, result);
return result;
}
/**
* Get svelte compile warnings
*/
function getSvelteCompileWarningsWithoutCache(context) {
const sourceCode = (0, compat_1.getSourceCode)(context);
// Process for styles
const styleElementsWithNotCSS = [...extractStyleElementsWithLangOtherThanCSS(context)];
const stripStyleElements = [];
const transformResults = [];
for (const style of styleElementsWithNotCSS) {
const transform = STYLE_TRANSFORMS[style.lang];
if (transform) {
const result = transform(style.node, (0, compat_1.getSourceCode)(context).text, context);
if (result) {
transformResults.push(result);
continue;
}
}
stripStyleElements.push(style.node);
}
const stripStyleTokens = stripStyleElements.flatMap((e) => e.children);
const ignoreComments = (0, ignore_comment_1.getSvelteIgnoreItems)(context).filter((item) => item.code != null);
const text = buildStrippedText(context, ignoreComments, stripStyleTokens);
transformResults.push(...transformScripts(context, text));
if (!transformResults.length) {
const warnings = getWarningsFromCode(text, context);
return {
...processIgnore(warnings.warnings, warnings.kind, stripStyleElements, ignoreComments, context),
kind: warnings.kind,
stripStyleElements
};
}
class RemapContext {
constructor() {
this.originalStart = 0;
this.code = '';
this.locs = null;
this.mapIndexes = [];
}
appendOriginal(endIndex) {
const codeStart = this.code.length;
const start = this.originalStart;
this.code += text.slice(start, endIndex);
this.originalStart = endIndex;
const offset = start - codeStart;
this.mapIndexes.push({
range: [codeStart, this.code.length],
remap(index) {
return index + offset;
}
});
}
postprocess() {
this.appendOriginal(text.length);
return this.code;
}
appendTranspile(output) {
const endIndex = output.inputRange[1];
const codeStart = this.code.length;
const start = this.originalStart;
const inputText = text.slice(start, endIndex);
const outputText = `${output.output}\n`;
this.code += outputText;
this.originalStart = endIndex;
let outputLocs = null;
let inputLocs = null;
let decoded = null;
this.mapIndexes.push({
range: [codeStart, this.code.length],
remap: (index) => {
outputLocs = outputLocs ?? new lines_and_columns_1.LinesAndColumns(outputText);
inputLocs = inputLocs ?? new lines_and_columns_1.LinesAndColumns(inputText);
const outputCodePos = outputLocs.getLocFromIndex(index - codeStart);
const inputCodePos = remapPosition(outputCodePos);
return inputLocs.getIndexFromLoc(inputCodePos) + start;
}
});
/** Remapping source position */
function remapPosition(pos) {
decoded = decoded ?? (0, sourcemap_codec_1.decode)(output.mappings);
const lineMaps = decoded[pos.line - 1];
if (!lineMaps?.length) {
for (let line = pos.line - 1; line >= 0; line--) {
const prevLineMaps = decoded[line];
if (prevLineMaps?.length) {
const [, , sourceCodeLine, sourceCodeColumn] = prevLineMaps[prevLineMaps.length - 1];
return {
line: sourceCodeLine + 1,
column: sourceCodeColumn
};
}
}
return { line: -1, column: -1 };
}
for (let index = 0; index < lineMaps.length - 1; index++) {
const [generateCodeColumn, , sourceCodeLine, sourceCodeColumn] = lineMaps[index];
if (generateCodeColumn <= pos.column && pos.column < lineMaps[index + 1][0]) {
return {
line: sourceCodeLine + 1,
column: sourceCodeColumn + (pos.column - generateCodeColumn)
};
}
}
const [generateCodeColumn, , sourceCodeLine, sourceCodeColumn] = lineMaps[lineMaps.length - 1];
return {
line: sourceCodeLine + 1,
column: sourceCodeColumn + (pos.column - generateCodeColumn)
};
}
}
remapLocs(points) {
const mapIndexes = this.mapIndexes;
const locs = (this.locs = this.locs ?? new lines_and_columns_1.LinesAndColumns(this.code));
let start = undefined;
let end = undefined;
if (points.start) {
const index = locs.getIndexFromLoc(points.start);
const remapped = remapIndex(index);
if (remapped) {
start = { ...sourceCode.getLocFromIndex(remapped), character: remapped };
}
}
if (points.end) {
const index = locs.getIndexFromLoc(points.end);
const remapped = remapIndex(index - 1 /* include index */);
if (remapped) {
const character = remapped + 1; /* restore */
end = { ...sourceCode.getLocFromIndex(character), character };
}
}
return { start, end };
/** remap index */
function remapIndex(index) {
for (const mapIndex of mapIndexes) {
if (mapIndex.range[0] <= index && index < mapIndex.range[1]) {
return mapIndex.remap(index);
}
}
return null;
}
}
}
const remapContext = new RemapContext();
for (const result of transformResults.sort((a, b) => a.inputRange[0] - b.inputRange[0])) {
remapContext.appendOriginal(result.inputRange[0]);
remapContext.appendTranspile(result);
}
const code = remapContext.postprocess();
const baseWarnings = getWarningsFromCode(code, context);
const warnings = [];
for (const warn of baseWarnings.warnings) {
let loc = null;
/** Get re-mapped location */
// eslint-disable-next-line func-style -- ignore
const getLoc = function getLoc() {
if (loc) {
return loc;
}
return (loc = remapContext.remapLocs(warn));
};
warnings.push({
code: warn.code,
message: warn.message,
get start() {
return getLoc().start;
},
get end() {
return getLoc().end;
}
});
}
return {
...processIgnore(warnings, baseWarnings.kind, stripStyleElements, ignoreComments, context),
kind: baseWarnings.kind,
stripStyleElements
};
}
/**
* Extracts the style with the lang attribute other than CSS.
*/
function* extractStyleElementsWithLangOtherThanCSS(context) {
const sourceCode = (0, compat_1.getSourceCode)(context);
const root = sourceCode.ast;
for (const node of root.body) {
if (node.type === 'SvelteStyleElement') {
const lang = (0, ast_utils_1.getLangValue)(node);
if (lang != null && lang.toLowerCase() !== 'css') {
yield { node, lang: lang.toLowerCase() };
}
}
}
}
/**
* Build the text stripped of tokens that are not needed for compilation.
*/
function buildStrippedText(context, ignoreComments, stripStyleTokens) {
const sourceCode = (0, compat_1.getSourceCode)(context);
const baseText = sourceCode.text;
const stripTokens = new Set([...ignoreComments.map((item) => item.token), ...stripStyleTokens]);
if (!stripTokens.size) {
return baseText;
}
let code = '';
let start = 0;
for (const token of [...stripTokens].sort((a, b) => a.range[0] - b.range[0])) {
code +=
baseText.slice(start, token.range[0]) +
baseText.slice(...token.range).replace(/[^\t\n\r ]/g, ' ');
start = token.range[1];
}
code += baseText.slice(start);
return code;
}
/** Returns the result of transforming the required script for the transform. */
function* transformScripts(context, text) {
const transform = isUseTypeScript(context)
? (0, typescript_1.hasTypeScript)(context)
? typescript_1.transform
: babel_1.transform
: isUseBabel(context)
? babel_1.transform
: null;
const sourceCode = (0, compat_1.getSourceCode)(context);
if (transform) {
const root = sourceCode.ast;
for (const node of root.body) {
if (node.type === 'SvelteScriptElement') {
const result = transform(node, text, context);
if (result) {
yield result;
}
}
}
}
}
function hasTagOption(program) {
return program.body.some((body) => {
if (body.type !== 'SvelteElement' || body.kind !== 'special') {
return false;
}
if (body.name.name !== 'svelte:options') {
return false;
}
return Boolean((0, ast_utils_1.findAttribute)(body, 'tag'));
});
}
/**
* Get compile warnings
*/
function getWarningsFromCode(code, context) {
try {
const result = compiler.compile(code, {
generate: false,
...(semver_1.default.satisfies(compiler.VERSION, '>=4.0.0-0')
? { customElement: true }
: hasTagOption((0, compat_1.getSourceCode)(context).ast)
? { customElement: true }
: {})
});
return { warnings: result.warnings, kind: 'warn' };
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- ignore
}
catch (e) {
return {
warnings: [
{
code: e.code,
message: e.message,
start: e.start,
end: e.end
}
],
kind: 'error'
};
}
}
/** Ignore process */
function processIgnore(warnings, kind, stripStyleElements, ignoreComments, context) {
if (kind === 'error') {
return {
warnings,
unusedIgnores: ignoreComments
};
}
const sourceCode = (0, compat_1.getSourceCode)(context);
const unusedIgnores = new Set(ignoreComments);
const remainingWarning = new Set(warnings);
for (const warning of warnings) {
if (!warning.code) {
continue;
}
let node = getWarningNode(warning);
while (node) {
for (const comment of (0, extract_leading_comments_1.extractLeadingComments)(context, node).reverse()) {
const ignoreItem = ignoreComments.find((item) => item.token === comment &&
(item.code === warning.code || item.codeForV5 === warning.code));
if (ignoreItem) {
unusedIgnores.delete(ignoreItem);
remainingWarning.delete(warning);
break;
}
}
node = getIgnoreParent(node);
}
}
// Stripped styles are ignored from compilation and cannot determine css errors.
for (const node of stripStyleElements) {
for (const comment of (0, extract_leading_comments_1.extractLeadingComments)(context, node).reverse()) {
const ignoreItem = ignoreComments.find((item) => item.token === comment &&
(CSS_WARN_CODES.has(item.code) || CSS_WARN_CODES.has(item.codeForV5)));
if (ignoreItem) {
unusedIgnores.delete(ignoreItem);
break;
}
}
}
return {
warnings: [...remainingWarning],
unusedIgnores: [...unusedIgnores]
};
/** Get ignore target parent node */
function getIgnoreParent(node) {
if (node.type !== 'SvelteElement' &&
node.type !== 'SvelteIfBlock' &&
node.type !== 'SvelteKeyBlock' &&
node.type !== 'SvelteEachBlock' &&
node.type !== 'SvelteAwaitBlock') {
return null;
}
const parent = node.parent;
if (parent.type === 'SvelteElseBlock') {
return parent.parent; // SvelteIfBlock or SvelteEachBlock
}
if (parent.type === 'SvelteAwaitPendingBlock' ||
parent.type === 'SvelteAwaitThenBlock' ||
parent.type === 'SvelteAwaitCatchBlock') {
return parent.parent; // SvelteAwaitBlock
}
if (parent.type !== 'SvelteElement' &&
parent.type !== 'SvelteIfBlock' &&
parent.type !== 'SvelteKeyBlock' &&
parent.type !== 'SvelteEachBlock'
// && parent.type !== "SvelteAwaitBlock"
) {
return null;
}
return parent;
}
/** Get warning node */
function getWarningNode(warning) {
const indexes = getWarningIndexes(warning);
if (indexes.start != null) {
const node = getWarningTargetNodeFromIndex(indexes.start);
if (node) {
return node;
}
if (indexes.end != null) {
const center = Math.floor(indexes.start + (indexes.end - indexes.start) / 2);
return getWarningTargetNodeFromIndex(center);
}
}
if (indexes.end != null) {
return getWarningTargetNodeFromIndex(indexes.end);
}
return null;
}
/**
* Get warning target node from the given index
*/
function getWarningTargetNodeFromIndex(index) {
let targetNode = sourceCode.getNodeByRangeIndex(index);
while (targetNode) {
if (targetNode.type === 'SvelteElement' || targetNode.type === 'SvelteStyleElement') {
return targetNode;
}
if (targetNode.parent) {
if (targetNode.parent.type === 'Program' ||
targetNode.parent.type === 'SvelteScriptElement') {
return targetNode;
}
}
else {
return null;
}
targetNode = targetNode.parent || null;
}
return null;
}
/** Get warning index */
function getWarningIndexes(warning) {
const start = warning.start && sourceCode.getIndexFromLoc(warning.start);
const end = warning.end && sourceCode.getIndexFromLoc(warning.end);
return { start, end };
}
}
/**
* Check if using TypeScript.
*/
function isUseTypeScript(context) {
const sourceCode = (0, compat_1.getSourceCode)(context);
if (sourceCode.parserServices.esTreeNodeToTSNodeMap)
return true;
const root = sourceCode.ast;
for (const node of root.body) {
if (node.type === 'SvelteScriptElement') {
const lang = (0, ast_utils_1.getLangValue)(node)?.toLowerCase();
if (lang === 'ts' || lang === 'typescript') {
return true;
}
}
}
return false;
}
/**
* Check if using Babel.
*/
function isUseBabel(context) {
const parser = context.parserOptions?.parser;
if (!parser) {
return false;
}
const sourceCode = (0, compat_1.getSourceCode)(context);
const root = sourceCode.ast;
let scriptLang = 'js';
for (const node of root.body) {
if (node.type === 'SvelteScriptElement') {
const lang = (0, ast_utils_1.getLangValue)(node)?.toLowerCase();
if (lang === 'ts' || lang === 'typescript') {
scriptLang = lang;
break;
}
}
}
const parserName = getParserName(scriptLang, parser);
if (!parserName) {
return false;
}
if (parserName === '@babel/eslint-parser') {
return true;
}
if (parserName.includes('@babel/eslint-parser')) {
let targetPath = parserName;
while (targetPath) {
const pkgPath = path_1.default.join(targetPath, 'package.json');
if (fs_1.default.existsSync(pkgPath)) {
try {
return JSON.parse(fs_1.default.readFileSync(pkgPath, 'utf-8'))?.name === '@babel/eslint-parser';
}
catch {
return false;
}
}
const parent = path_1.default.dirname(targetPath);
if (targetPath === parent) {
break;
}
targetPath = parent;
}
}
return false;
/** Get script parser name */
function getParserName(lang, parser) {
if (typeof parser === 'string') {
return parser;
}
else if (typeof parser === 'object') {
const name = parser[lang];
if (typeof name === 'string') {
return name;
}
}
return null;
}
}

View File

@@ -0,0 +1,9 @@
import type { AST } from 'svelte-eslint-parser';
import type { RuleContext } from '../../../types';
import type { TransformResult } from './types';
/**
* Transpile with babel
*/
export declare function transform(node: AST.SvelteScriptElement, text: string, context: RuleContext): TransformResult | null;
/** Check if project has Babel. */
export declare function hasBabel(context: RuleContext): boolean;

View File

@@ -0,0 +1,54 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.transform = transform;
exports.hasBabel = hasBabel;
const load_module_1 = require("../../../utils/load-module");
const compat_1 = require("../../../utils/compat");
/**
* Transpile with babel
*/
function transform(node, text, context) {
const babel = loadBabel(context);
if (!babel) {
return null;
}
let inputRange;
if (node.endTag) {
inputRange = [node.startTag.range[1], node.endTag.range[0]];
}
else {
inputRange = [node.startTag.range[1], node.range[1]];
}
const code = text.slice(...inputRange);
try {
const output = babel.transformSync(code, {
sourceType: 'module',
sourceMaps: true,
minified: false,
ast: false,
code: true,
cwd: (0, compat_1.getCwd)(context)
});
if (!output) {
return null;
}
return {
inputRange,
output: output.code,
mappings: output.map.mappings
};
}
catch (_e) {
return null;
}
}
/** Check if project has Babel. */
function hasBabel(context) {
return Boolean(loadBabel(context));
}
/**
* Load babel
*/
function loadBabel(context) {
return (0, load_module_1.loadModule)(context, '@babel/core');
}

View File

@@ -0,0 +1,7 @@
import type { AST } from 'svelte-eslint-parser';
import type { RuleContext } from '../../../types';
import type { TransformResult } from './types';
/**
* Transpile with less
*/
export declare function transform(node: AST.SvelteStyleElement, text: string, context: RuleContext): TransformResult | null;

View File

@@ -0,0 +1,51 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.transform = transform;
const load_module_1 = require("../../../utils/load-module");
const compat_1 = require("../../../utils/compat");
/**
* Transpile with less
*/
function transform(node, text, context) {
const less = loadLess(context);
if (!less) {
return null;
}
let inputRange;
if (node.endTag) {
inputRange = [node.startTag.range[1], node.endTag.range[0]];
}
else {
inputRange = [node.startTag.range[1], node.range[1]];
}
const code = text.slice(...inputRange);
const filename = `${(0, compat_1.getFilename)(context)}.less`;
try {
let output;
less.render(code, {
sourceMap: {},
syncImport: true,
filename,
lint: false
}, (_error, result) => {
output = result;
});
if (!output) {
return null;
}
return {
inputRange,
output: output.css,
mappings: JSON.parse(output.map).mappings
};
}
catch (_e) {
return null;
}
}
/**
* Load less
*/
function loadLess(context) {
return (0, load_module_1.loadModule)(context, 'less');
}

View File

@@ -0,0 +1,7 @@
import type { AST } from 'svelte-eslint-parser';
import type { RuleContext } from '../../../types';
import type { TransformResult } from './types';
/**
* Transform with postcss
*/
export declare function transform(node: AST.SvelteStyleElement, text: string, context: RuleContext): TransformResult | null;

View File

@@ -0,0 +1,49 @@
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.transform = transform;
const postcss_1 = __importDefault(require("postcss"));
const postcss_load_config_1 = __importDefault(require("postcss-load-config"));
const compat_1 = require("../../../utils/compat");
/**
* Transform with postcss
*/
function transform(node, text, context) {
const postcssConfig = context.settings?.svelte?.compileOptions?.postcss;
if (postcssConfig === false) {
return null;
}
let inputRange;
if (node.endTag) {
inputRange = [node.startTag.range[1], node.endTag.range[0]];
}
else {
inputRange = [node.startTag.range[1], node.range[1]];
}
const code = text.slice(...inputRange);
const filename = `${(0, compat_1.getFilename)(context)}.css`;
try {
const configFilePath = postcssConfig?.configFilePath;
const config = postcss_load_config_1.default.sync({
cwd: (0, compat_1.getCwd)(context),
from: filename
}, typeof configFilePath === 'string' ? configFilePath : undefined);
const result = (0, postcss_1.default)(config.plugins).process(code, {
...config.options,
map: {
inline: false
}
});
return {
inputRange,
output: result.content,
mappings: result.map.toJSON().mappings
};
}
catch (_e) {
// console.log(e)
return null;
}
}

View File

@@ -0,0 +1,7 @@
import type { AST } from 'svelte-eslint-parser';
import type { RuleContext } from '../../../types';
import type { TransformResult } from './types';
/**
* Transpile with sass
*/
export declare function transform(node: AST.SvelteStyleElement, text: string, context: RuleContext, type: 'scss' | 'sass'): TransformResult | null;

View File

@@ -0,0 +1,44 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.transform = transform;
const load_module_1 = require("../../../utils/load-module");
/**
* Transpile with sass
*/
function transform(node, text, context, type) {
const sass = loadSass(context);
if (!sass) {
return null;
}
let inputRange;
if (node.endTag) {
inputRange = [node.startTag.range[1], node.endTag.range[0]];
}
else {
inputRange = [node.startTag.range[1], node.range[1]];
}
const code = text.slice(...inputRange);
try {
const output = sass.compileString(code, {
sourceMap: true,
syntax: type === 'sass' ? 'indented' : undefined
});
if (!output) {
return null;
}
return {
inputRange,
output: output.css,
mappings: output.sourceMap.mappings
};
}
catch (_e) {
return null;
}
}
/**
* Load sass
*/
function loadSass(context) {
return (0, load_module_1.loadModule)(context, 'sass');
}

View File

@@ -0,0 +1,7 @@
import type { AST } from 'svelte-eslint-parser';
import type { RuleContext } from '../../../types';
import type { TransformResult } from './types';
/**
* Transpile with stylus
*/
export declare function transform(node: AST.SvelteStyleElement, text: string, context: RuleContext): TransformResult | null;

View File

@@ -0,0 +1,49 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.transform = transform;
const load_module_1 = require("../../../utils/load-module");
const compat_1 = require("../../../utils/compat");
/**
* Transpile with stylus
*/
function transform(node, text, context) {
const stylus = loadStylus(context);
if (!stylus) {
return null;
}
let inputRange;
if (node.endTag) {
inputRange = [node.startTag.range[1], node.endTag.range[0]];
}
else {
inputRange = [node.startTag.range[1], node.range[1]];
}
const code = text.slice(...inputRange);
const filename = `${(0, compat_1.getFilename)(context)}.stylus`;
try {
let output;
const style = stylus(code, {
filename
}).set('sourcemap', {});
style.render((_error, code) => {
output = code;
});
if (output == null) {
return null;
}
return {
inputRange,
output,
mappings: style.sourcemap.mappings
};
}
catch (_e) {
return null;
}
}
/**
* Load stylus
*/
function loadStylus(context) {
return (0, load_module_1.loadModule)(context, 'stylus');
}

View File

@@ -0,0 +1,6 @@
import type { AST } from 'svelte-eslint-parser';
export type TransformResult = {
inputRange: AST.Range;
output: string;
mappings: string;
};

View File

@@ -0,0 +1,2 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });

View File

@@ -0,0 +1,9 @@
import type { AST } from 'svelte-eslint-parser';
import type { RuleContext } from '../../../types';
import type { TransformResult } from './types';
/**
* Transpile with typescript
*/
export declare function transform(node: AST.SvelteScriptElement, text: string, context: RuleContext): TransformResult | null;
/** Check if project has TypeScript. */
export declare function hasTypeScript(context: RuleContext): boolean;

View File

@@ -0,0 +1,55 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.transform = transform;
exports.hasTypeScript = hasTypeScript;
const load_module_1 = require("../../../utils/load-module");
const compat_1 = require("../../../utils/compat");
/**
* Transpile with typescript
*/
function transform(node, text, context) {
const ts = loadTs(context);
if (!ts) {
return null;
}
let inputRange;
if (node.endTag) {
inputRange = [node.startTag.range[1], node.endTag.range[0]];
}
else {
inputRange = [node.startTag.range[1], node.range[1]];
}
const code = text.slice(...inputRange);
try {
const output = ts.transpileModule(code, {
reportDiagnostics: false,
compilerOptions: {
target: (0, compat_1.getSourceCode)(context).parserServices.program?.getCompilerOptions()?.target ||
ts.ScriptTarget.ESNext,
module: ts.ModuleKind.ESNext,
importsNotUsedAsValues: ts.ImportsNotUsedAsValues.Preserve,
preserveValueImports: true,
verbatimModuleSyntax: true,
sourceMap: true
}
});
return {
inputRange,
output: output.outputText,
mappings: JSON.parse(output.sourceMapText).mappings
};
}
catch {
return null;
}
}
/** Check if project has TypeScript. */
function hasTypeScript(context) {
return Boolean(loadTs(context));
}
/**
* Load typescript
*/
function loadTs(context) {
return (0, load_module_1.loadModule)(context, 'typescript');
}