rules/eslint/no-port-literal.eslint.rule.ts

rules/eslint/no-port-literal.eslint.rule.ts is a file in GovLab Extension Host. 186 lines of code and 46 definitions.

import type { LocalRule, RuleContext, RuleListener } from "../../types/rule.types.ts";
import {
    argumentAt,
    calleeName,
    isType,
    locOf,
    nameOf,
    nodeAt,
    nodesAt,
    numberAt,
    propertyKeyName,
    recordAt,
    staticTextOf,
    stringAt,
    stringIn,
    walk,
} from "../../shared/selectors/syntax.selector.ts";
import type { AstNode } from "../../types/syntax.types.ts";
import { defineCheck } from "@govlab/context/check";
import { listener } from "../../shared/factories/listener.factory.ts";

const PORT_WORD = "port";
const WORD_BREAK = "_";
const DIGITS = "0123456789";
const HOST_MARKS: readonly string[] = ["localhost:", "127.0.0.1:", "0.0.0.0:", "-port="];
const FALLBACK_OPERATORS: ReadonlySet<string> = new Set(["??", "||"]);
const LISTEN_CALLEES: ReadonlySet<string> = new Set(["listen"]);
const SYSTEM_ASSIGNED = 0;

const segmentsOf = function segmentsOf(name: string): readonly string[] {
    const segments: string[] = [];
    let current = "";
    for (const char of name) {
        const upper = char !== char.toLowerCase();
        if (char === WORD_BREAK || (upper && current.length > 0 && current !== current.toUpperCase())) {
            segments.push(current.toLowerCase());
            current = char === WORD_BREAK ? "" : char;
            continue;
        }
        current += char;
    }
    return [...segments, current.toLowerCase()].filter((segment) => segment.length > 0);
};

const isPortName = function isPortName(name: string): boolean {
    return segmentsOf(name).includes(PORT_WORD);
};

const isDigits = function isDigits(text: string): boolean {
    for (let at = 0; at < text.length; at += 1) {
        if (!DIGITS.includes(text.charAt(at))) {
            return false;
        }
    }
    return text.length > 0;
};

const SYSTEM_ASSIGNED_TEXT = String(SYSTEM_ASSIGNED);

const isPortLiteral = function isPortLiteral(node: AstNode | null): boolean {
    if (!isType(node, "Literal") && !isType(node, "TemplateLiteral")) {
        return false;
    }
    const value = numberAt(node, "value");
    if (value !== null) {
        return value !== SYSTEM_ASSIGNED;
    }
    const text = staticTextOf(node);
    return text !== null && isDigits(text) && text !== SYSTEM_ASSIGNED_TEXT;
};

const digitRunAt = function digitRunAt(text: string, start: number): string {
    let end = start;
    while (end < text.length && DIGITS.includes(text.charAt(end))) {
        end += 1;
    }
    return text.slice(start, end);
};

const namesPort = function namesPort(node: AstNode | null): boolean {
    if (node === null) {
        return false;
    }
    let found = false;
    walk(node, (inner) => {
        if (isType(inner, "Identifier") && isPortName(nameOf(inner))) {
            found = true;
        }
    });
    return found;
};

const hostPortIn = function hostPortIn(text: string): string | null {
    for (const mark of HOST_MARKS) {
        const at = text.indexOf(mark);
        const run = at === -1 ? "" : digitRunAt(text, at + mark.length);
        if (run.length > 0 && run !== SYSTEM_ASSIGNED_TEXT) {
            return mark;
        }
    }
    return null;
};

const quasiTexts = function quasiTexts(node: AstNode): string {
    return nodesAt(node, "quasis")
        .map((part) => stringIn(recordAt(part, "value"), "cooked"))
        .join("");
};

export default {
    create(context: RuleContext): RuleListener {
        const reportLiteral = function reportLiteral(node: AstNode, name: string): void {
            context.report({ data: { name }, loc: locOf(node), messageId: "portLiteral" });
        };
        const reportHost = function reportHost(node: AstNode, text: string): void {
            const mark = hostPortIn(text);
            if (mark !== null) {
                context.report({ data: { mark }, loc: locOf(node), messageId: "hostPort" });
            }
        };
        return listener({
            assignmentExpression(view) {
                const left = nodeAt(view, "left");
                const right = nodeAt(view, "right");
                if (isType(left, "Identifier") && isPortName(nameOf(left)) && isPortLiteral(right) && right !== null) {
                    reportLiteral(right, nameOf(left));
                }
            },
            callExpression(view) {
                const callee = calleeName(view);
                const first = argumentAt(view, 0);
                if (first === null || !isPortLiteral(first)) {
                    return;
                }
                const listensOnLiteral = LISTEN_CALLEES.has(callee) && numberAt(first, "value") !== SYSTEM_ASSIGNED;
                if (isPortName(callee) || listensOnLiteral) {
                    reportLiteral(first, callee);
                }
            },
            literal(view) {
                const text = staticTextOf(view);
                if (text !== null) {
                    reportHost(view, text);
                }
            },
            logicalExpression(view) {
                if (!FALLBACK_OPERATORS.has(stringAt(view, "operator"))) {
                    return;
                }
                const left = nodeAt(view, "left");
                const right = nodeAt(view, "right");
                const literalSide = isPortLiteral(right) ? right : (isPortLiteral(left) ? left : null);
                const readSide = literalSide === right ? left : right;
                if (literalSide !== null && namesPort(readSide)) {
                    context.report({ loc: locOf(literalSide), messageId: "portFallback" });
                }
            },
            property(view) {
                const value = nodeAt(view, "value");
                const key = propertyKeyName(view);
                if (isPortName(key) && value !== null && isPortLiteral(value)) {
                    reportLiteral(value, key);
                }
            },
            templateLiteral(view) {
                reportHost(view, quasiTexts(view));
            },
            variableDeclarator(view) {
                const id = nodeAt(view, "id");
                const init = nodeAt(view, "init");
                if (isType(id, "Identifier") && isPortName(nameOf(id)) && init !== null && isPortLiteral(init)) {
                    reportLiteral(init, nameOf(id));
                }
            },
        });
    },
    meta: {
        docs: {
            checks: defineCheck({
                detects: ["architecture:hardcoded-configuration"],
                enforces: ["architecture:configuration-externalization"],
            }),
            description:
                "A network port is never written in source. Every port is read from the secret store through its typed accessor, and a missing value stops the run instead of falling back. The rule refuses a port-named variable, property, assignment or call given a number or a digit string, a listen call given a literal port, a fallback between a port read and a literal, and a host with a port or a port flag written into a string. Port 0 passes in every form, because it asks the system for a free port and names none. A name is port-named when one of its word segments is port, so report, import and support pass.",
        },
        messages: {
            hostPort:
                "The string writes a port after '{{mark}}'. Build the address from the port the secret store's accessor returns.",
            portFallback:
                "A port read falls back to a literal. Remove the fallback, because the secret store's accessor stops the run when the store does not hold the port.",
            portLiteral:
                "{{name}} is given a port as a literal. Read the port from the secret store through its typed accessor, and declare its key in the store's schema.",
        },
        schema: [],
        type: "problem",
    },
} satisfies LocalRule;