Files
oikos/web/node_modules/eslint/lib/rules/no-nonoctal-decimal-escape.js
dtoro d4d99a7473
Some checks failed
ci / build-test (push) Has been cancelled
ci / docker-build (push) Has been cancelled
feat: Phase 1 — extract the client (web SPA + desktop) to dtoro/oikos-web
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.
2026-08-15 22:27:52 +02:00

177 lines
4.5 KiB
JavaScript

/**
* @fileoverview Rule to disallow `\8` and `\9` escape sequences in string literals.
* @author Milos Djermanovic
*/
"use strict";
//------------------------------------------------------------------------------
// Typedefs
//------------------------------------------------------------------------------
/**
* @import { SourceRange } from "@eslint/core";
*/
//------------------------------------------------------------------------------
// Helpers
//------------------------------------------------------------------------------
const QUICK_TEST_REGEX = /\\[89]/u;
/**
* Returns unicode escape sequence that represents the given character.
* @param {string} character A single code unit.
* @returns {string} "\uXXXX" sequence.
*/
function getUnicodeEscape(character) {
return `\\u${character.charCodeAt(0).toString(16).padStart(4, "0")}`;
}
//------------------------------------------------------------------------------
// Rule Definition
//------------------------------------------------------------------------------
/** @type {import('../types').Rule.RuleModule} */
module.exports = {
meta: {
type: "suggestion",
docs: {
description:
"Disallow `\\8` and `\\9` escape sequences in string literals",
recommended: true,
url: "https://eslint.org/docs/latest/rules/no-nonoctal-decimal-escape",
},
hasSuggestions: true,
schema: [],
messages: {
decimalEscape: "Don't use '{{decimalEscape}}' escape sequence.",
// suggestions
refactor:
"Replace '{{original}}' with '{{replacement}}'. This maintains the current functionality.",
escapeBackslash:
"Replace '{{original}}' with '{{replacement}}' to include the actual backslash character.",
},
},
create(context) {
const sourceCode = context.sourceCode;
/**
* Creates a new Suggestion object.
* @param {string} messageId "refactor" or "escapeBackslash".
* @param {SourceRange} range The range to replace.
* @param {string} replacement New text for the range.
* @returns {Object} Suggestion
*/
function createSuggestion(messageId, range, replacement) {
return {
messageId,
data: {
original: sourceCode.getText().slice(...range),
replacement,
},
fix(fixer) {
return fixer.replaceTextRange(range, replacement);
},
};
}
return {
Literal(node) {
if (typeof node.value !== "string") {
return;
}
if (!QUICK_TEST_REGEX.test(node.raw)) {
return;
}
const regex =
/(?:[^\\]|(?<previousEscape>\\.))*?(?<decimalEscape>\\[89])/suy;
let match;
while ((match = regex.exec(node.raw))) {
const { previousEscape, decimalEscape } = match.groups;
const decimalEscapeRangeEnd =
node.range[0] + match.index + match[0].length;
const decimalEscapeRangeStart =
decimalEscapeRangeEnd - decimalEscape.length;
const decimalEscapeRange = [
decimalEscapeRangeStart,
decimalEscapeRangeEnd,
];
const suggest = [];
// When `regex` is matched, `previousEscape` can only capture characters adjacent to `decimalEscape`
if (previousEscape === "\\0") {
/*
* Now we have a NULL escape "\0" immediately followed by a decimal escape, e.g.: "\0\8".
* Fixing this to "\08" would turn "\0" into a legacy octal escape. To avoid producing
* an octal escape while fixing a decimal escape, we provide different suggestions.
*/
suggest.push(
createSuggestion(
// "\0\8" -> "\u00008"
"refactor",
[
decimalEscapeRangeStart -
previousEscape.length,
decimalEscapeRangeEnd,
],
`${getUnicodeEscape("\0")}${decimalEscape[1]}`,
),
createSuggestion(
// "\8" -> "\u0038"
"refactor",
decimalEscapeRange,
getUnicodeEscape(decimalEscape[1]),
),
);
} else {
suggest.push(
createSuggestion(
// "\8" -> "8"
"refactor",
decimalEscapeRange,
decimalEscape[1],
),
);
}
suggest.push(
createSuggestion(
// "\8" -> "\\8"
"escapeBackslash",
decimalEscapeRange,
`\\${decimalEscape}`,
),
);
context.report({
node,
loc: {
start: sourceCode.getLocFromIndex(
decimalEscapeRangeStart,
),
end: sourceCode.getLocFromIndex(
decimalEscapeRangeEnd,
),
},
messageId: "decimalEscape",
data: {
decimalEscape,
},
suggest,
});
}
},
};
},
};