Files
oikos/web/node_modules/eslint/lib/rules/no-control-regex.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

143 lines
3.7 KiB
JavaScript

/**
* @fileoverview Rule to forbid control characters from regular expressions.
* @author Nicholas C. Zakas
*/
"use strict";
const RegExpValidator = require("@eslint-community/regexpp").RegExpValidator;
const collector = new (class {
constructor() {
this._source = "";
this._controlChars = [];
this._validator = new RegExpValidator(this);
}
onPatternEnter() {
/*
* `RegExpValidator` may parse the pattern twice in one `validatePattern`.
* So `this._controlChars` should be cleared here as well.
*
* For example, the `/(?<a>\x1f)/` regex will parse the pattern twice.
* This is based on the content described in Annex B.
* If the regex contains a `GroupName` and the `u` flag is not used, `ParseText` will be called twice.
* See https://tc39.es/ecma262/2023/multipage/additional-ecmascript-features-for-web-browsers.html#sec-parsepattern-annexb
*/
this._controlChars = [];
}
onCharacter(start, end, cp) {
if (
cp >= 0x00 &&
cp <= 0x1f &&
(this._source.codePointAt(start) === cp ||
this._source.slice(start, end).startsWith("\\x") ||
this._source.slice(start, end).startsWith("\\u"))
) {
this._controlChars.push(`\\x${`0${cp.toString(16)}`.slice(-2)}`);
}
}
collectControlChars(regexpStr, flags) {
const uFlag = typeof flags === "string" && flags.includes("u");
const vFlag = typeof flags === "string" && flags.includes("v");
this._controlChars = [];
this._source = regexpStr;
try {
this._validator.validatePattern(regexpStr, void 0, void 0, {
unicode: uFlag,
unicodeSets: vFlag,
}); // Call onCharacter hook
} catch {
// Ignore syntax errors in RegExp.
}
return this._controlChars;
}
})();
//------------------------------------------------------------------------------
// Rule Definition
//------------------------------------------------------------------------------
/** @type {import('../types').Rule.RuleModule} */
module.exports = {
meta: {
type: "problem",
docs: {
description: "Disallow control characters in regular expressions",
recommended: true,
url: "https://eslint.org/docs/latest/rules/no-control-regex",
},
schema: [],
messages: {
unexpected:
"Unexpected control character(s) in regular expression: {{controlChars}}.",
},
},
create(context) {
/**
* Get the regex expression
* @param {ASTNode} node `Literal` node to evaluate
* @returns {{ pattern: string, flags: string | null } | null} Regex if found (the given node is either a regex literal
* or a string literal that is the pattern argument of a RegExp constructor call). Otherwise `null`. If flags cannot be determined,
* the `flags` property will be `null`.
* @private
*/
function getRegExp(node) {
if (node.regex) {
return node.regex;
}
if (
typeof node.value === "string" &&
(node.parent.type === "NewExpression" ||
node.parent.type === "CallExpression") &&
node.parent.callee.type === "Identifier" &&
node.parent.callee.name === "RegExp" &&
node.parent.arguments[0] === node
) {
const pattern = node.value;
const flags =
node.parent.arguments.length > 1 &&
node.parent.arguments[1].type === "Literal" &&
typeof node.parent.arguments[1].value === "string"
? node.parent.arguments[1].value
: null;
return { pattern, flags };
}
return null;
}
return {
Literal(node) {
const regExp = getRegExp(node);
if (regExp) {
const { pattern, flags } = regExp;
const controlCharacters = collector.collectControlChars(
pattern,
flags,
);
if (controlCharacters.length > 0) {
context.report({
node,
messageId: "unexpected",
data: {
controlChars: controlCharacters.join(", "),
},
});
}
}
},
};
},
};