@sarj/eslint-plugin 16.36.1 → 16.36.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +61 -8
- package/dist/index.js +61 -8
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -1928,7 +1928,7 @@ var NO_TEST_COMMENTS_DOCUMENTATION = {
|
|
|
1928
1928
|
autofix: "safe",
|
|
1929
1929
|
limitations: [
|
|
1930
1930
|
"Checks actual line and block comment tokens in recognized maintained test files, including test fixtures; strings and template text remain unchanged. Generated/vendor files are excluded.",
|
|
1931
|
-
"Legal notices, shebangs, TypeScript triple-slash references, coverage/build annotations and explicit lint/type/test/formatter directives remain allowed.
|
|
1931
|
+
"Legal notices, shebangs, TypeScript triple-slash references, coverage/build annotations and explicit lint/type/test/formatter directives remain allowed. Executable detect-secrets 1.5.0 allowlist/whitelist pragmas retain its case-sensitive delimiter, spacing, physical-line and next-line grammar; arbitrary pragma prose remains ordinary commentary. Rationale on an executable directive remains the owning tool's responsibility.",
|
|
1932
1932
|
"The automatic fix removes comments and adjacent padding while retaining every line ending and necessary token boundary; it does not rename tests or remove executable statements. Bare integration/e2e directories and ambiguous hyphen/underscore test basenames require another concrete test-path signal or a runtime testing-framework import; ordinary production integration modules and rule implementations ending in -test are preserved."
|
|
1933
1933
|
],
|
|
1934
1934
|
examples: [
|
|
@@ -1939,6 +1939,52 @@ var NO_TEST_COMMENTS_DOCUMENTATION = {
|
|
|
1939
1939
|
var DIRECTIVE = /^(?:eslint(?:-\S+)?\b|(?:biome|oxlint|jshint|jslint|c8|v8|istanbul|vitest|prettier|dprint)\b|@(?:ts-(?:check|nocheck|expect-error|ignore)|vitest-environment|jest-environment|jsx\w*|license|preserve)\b|[#!]\s*__(?:PURE|NO_SIDE_EFFECTS)__|\/\s*<(?:reference|amd-module|amd-dependency)\b)/u;
|
|
1940
1940
|
var LEGAL = /(?:^|\n)\s*\*?\s*(?:Copyright\b|SPDX-License-Identifier:|Licensed under\b|@license\b|@preserve\b)/iu;
|
|
1941
1941
|
var TYPE_CONTRACT = /@(?:type|typedef|template|param|returns?|satisfies|callback|overload|implements|extends|augments|enum|this|import)\b/u;
|
|
1942
|
+
var DETECT_SECRETS_COMMENT_MARKERS = [
|
|
1943
|
+
{ start: "#", end: "" },
|
|
1944
|
+
{ start: "//", end: "" },
|
|
1945
|
+
{ start: "/\\*", end: " *\\*/" },
|
|
1946
|
+
{ start: "'", end: "" },
|
|
1947
|
+
{ start: "--", end: "" },
|
|
1948
|
+
{ start: "<!--[# \\t]*?", end: " *?-->" }
|
|
1949
|
+
];
|
|
1950
|
+
var DETECT_SECRETS_PRAGMAS = DETECT_SECRETS_COMMENT_MARKERS.flatMap(({ start, end }) => [
|
|
1951
|
+
{ nextLine: false, pattern: new RegExp(`[ \\t]*${start} *pragma: ?(?:allow|white)list[ -]secret[^\\n]*?${end}[ \\t]*(?![\\s\\S])`, "u") },
|
|
1952
|
+
{ nextLine: true, pattern: new RegExp(`^[ \\t]*${start} *pragma: ?allowlist[ -]nextline[ -]secret[^\\n]*?${end}[ \\t]*(?![\\s\\S])`, "u") }
|
|
1953
|
+
]);
|
|
1954
|
+
var PYTHON_WHITESPACE = new RegExp("\\p{White_Space}", "u");
|
|
1955
|
+
var PYTHON_EXTRA_WHITESPACE = /* @__PURE__ */ new Set(["", "", "", ""]);
|
|
1956
|
+
function hasDetectSecretsPragma(comment, text) {
|
|
1957
|
+
if (!comment.value.includes("pragma:")) return false;
|
|
1958
|
+
let lineStart = Math.max(text.lastIndexOf("\r", comment.range[0] - 1), text.lastIndexOf("\n", comment.range[0] - 1)) + 1;
|
|
1959
|
+
while (lineStart < comment.range[1]) {
|
|
1960
|
+
const nextCr = text.indexOf("\r", lineStart);
|
|
1961
|
+
const nextLf = text.indexOf("\n", lineStart);
|
|
1962
|
+
const lineEnd = Math.min(nextCr < 0 ? text.length : nextCr, nextLf < 0 ? text.length : nextLf);
|
|
1963
|
+
if (hasPragmaInReaderLine(comment, text.slice(lineStart, lineEnd), lineStart)) return true;
|
|
1964
|
+
lineStart = lineEnd + (text[lineEnd] === "\r" && text[lineEnd + 1] === "\n" ? 2 : 1);
|
|
1965
|
+
}
|
|
1966
|
+
return false;
|
|
1967
|
+
}
|
|
1968
|
+
function hasPragmaInReaderLine(comment, rawLine, lineStart) {
|
|
1969
|
+
const sameLine = stripPythonTrailingWhitespace(rawLine);
|
|
1970
|
+
for (const { nextLine, pattern } of DETECT_SECRETS_PRAGMAS) {
|
|
1971
|
+
const column = nextLine ? 0 : Math.max(0, comment.range[0] - lineStart);
|
|
1972
|
+
const match = pattern.exec((nextLine ? rawLine : sameLine).slice(column));
|
|
1973
|
+
if (match === null) continue;
|
|
1974
|
+
const pragmaStart = lineStart + column + match.index + match[0].indexOf("pragma:");
|
|
1975
|
+
if (pragmaStart >= comment.range[0] && pragmaStart < comment.range[1]) return true;
|
|
1976
|
+
}
|
|
1977
|
+
return false;
|
|
1978
|
+
}
|
|
1979
|
+
function stripPythonTrailingWhitespace(value) {
|
|
1980
|
+
let end = value.length;
|
|
1981
|
+
while (end > 0) {
|
|
1982
|
+
const character = value.charAt(end - 1);
|
|
1983
|
+
if (!PYTHON_WHITESPACE.test(character) && !PYTHON_EXTRA_WHITESPACE.has(character)) break;
|
|
1984
|
+
end--;
|
|
1985
|
+
}
|
|
1986
|
+
return value.slice(0, end);
|
|
1987
|
+
}
|
|
1942
1988
|
var no_test_comments_default = createRule({
|
|
1943
1989
|
name: "no-test-comments",
|
|
1944
1990
|
documentation: NO_TEST_COMMENTS_DOCUMENTATION,
|
|
@@ -1975,7 +2021,7 @@ var no_test_comments_default = createRule({
|
|
|
1975
2021
|
}
|
|
1976
2022
|
for (const [index, comment] of comments.entries()) {
|
|
1977
2023
|
const body3 = comment.value.trim().replace(/^\*\s*/u, "");
|
|
1978
|
-
if (DIRECTIVE.test(body3) || LEGAL.test(comment.value) || legalLines.has(index) || TYPE_CONTRACT.test(comment.value)) continue;
|
|
2024
|
+
if (DIRECTIVE.test(body3) || LEGAL.test(comment.value) || legalLines.has(index) || TYPE_CONTRACT.test(comment.value) || hasDetectSecretsPragma(comment, context.sourceCode.text)) continue;
|
|
1979
2025
|
context.report({
|
|
1980
2026
|
node: comment,
|
|
1981
2027
|
messageId: "testComment",
|
|
@@ -2773,6 +2819,7 @@ var import_typescript8 = __toESM(require("typescript"), 1);
|
|
|
2773
2819
|
var CONFIG2 = { path: "tsconfig.json", source: '{"compilerOptions":{"strict":true,"target":"ESNext","module":"ESNext","moduleResolution":"Bundler"}}' };
|
|
2774
2820
|
var EXAMPLE2 = 'export {}; const account = {name: "Ada", email: "a"}; function label(user: {readonly email: string; readonly name: string}): string {return user.name;} label(account);';
|
|
2775
2821
|
var HELPER_EXAMPLE = 'export {}; const account = {name: "Ada", email: "a"}; function formatName(name: string): string {return name.toUpperCase();} function label(user: {email: string; name: string}): string {return formatName(user.name);} label(account);';
|
|
2822
|
+
var NULLABLE_EXAMPLE = 'export {}; const account = {name: "Ada", email: null}; function label(user: {email: string | null; name: string}): string {return user.name;} label(account);';
|
|
2776
2823
|
var PREFER_MINIMAL_PARAMETER_CONTRACT_DOCUMENTATION = {
|
|
2777
2824
|
defaultLevel: "warning",
|
|
2778
2825
|
summary: "Prefer the proven read-only subset for a private consumer parameter.",
|
|
@@ -2783,26 +2830,32 @@ var PREFER_MINIMAL_PARAMETER_CONTRACT_DOCUMENTATION = {
|
|
|
2783
2830
|
limitations: [
|
|
2784
2831
|
"Automatic fixes additionally require every existing identifier caller to be natively assignable to the original contract, without Any, unknown, error or unresolved types on the relevant surface. Comments inside the annotation and TypeScript suppression directives keep the finding manual. Only unread required property signatures are removed; every read or optional member, modifier and terminator token is preserved. Edits that would merge distinct CR and LF terminators remain manual.",
|
|
2785
2832
|
"Requires parser-owned TypeScript information and a strict external module. Only top-level private function declarations and raw const arrows with exactly one explicit parameter and an independent broad primitive return annotation are supported. Every callable reference must be a direct call with one identifier argument; exports including type-only exports, typeof queries, callbacks, storage, constructors, contextual signatures, overloads, generics, decorators, async functions and direct eval prevent findings.",
|
|
2786
|
-
"Only anonymous inline contracts made of broad string, number, boolean or bigint property types are supported. Fields may be plain or readonly; the consumer body must still prove read-only usage. Retained field modifiers and optional members are preserved. Named domain contracts, brands, literal discriminants, classes, methods, callbacks, indexes, mapped types and imported parameter contracts are excluded. At least one read required field and one unread required field must be established; all-optional reduced contracts can reject existing callers through weak-type checks.",
|
|
2833
|
+
"Only anonymous inline contracts made of broad string, number, boolean or bigint property types are supported. An unread required field may additionally have exactly one such keyword and null or undefined in a direct two-part union; read or optional nullable fields and more complex unions remain excluded. Fields may be plain or readonly; the consumer body must still prove read-only usage. Retained field modifiers and optional members are preserved. Named domain contracts, brands, literal discriminants, classes, methods, callbacks, indexes, mapped types and imported parameter contracts are excluded. At least one read required field and one unread required field must be established; all-optional reduced contracts can reject existing callers through weak-type checks.",
|
|
2787
2834
|
"The single return expression may use only fixed scalar field reads, flat destructuring, literals, operators, templates and scalar calls to same-file implemented top-level functions or raw const arrows with an explicit return annotation. These calls require one native resolved declaration, no generic or predicate signature, scalar arguments/result and complete active native checking of the local implementation file. Their helper bodies need not be pure because only scalar values cross the call and only an erased parameter annotation changes. Imported, aliased, ambient, overloaded, optional, spread and member calls remain unsupported. Bound destructured fields count as reads even when their local aliases are unused. Whole-object escape, identity, mutation, closures, reflection, serialization, dynamic keys, implicit Object capabilities and JSX are unsupported. Unknown semantic information causes abstention. Findings outside the fix proof remain manual; this is not a requirement to minimize every object or discard useful producer fields."
|
|
2788
2835
|
],
|
|
2789
2836
|
examples: [
|
|
2790
2837
|
{ id: "unused-required-field", title: "A private name consumer unnecessarily requires email", outcome: "match", files: [{ path: "src/case.ts", source: EXAMPLE2 }, CONFIG2], focusPath: "src/case.ts", expectedCount: 1, public: true, fixedFiles: [{ path: "src/case.ts", source: EXAMPLE2.replace("readonly email: string;", "") }] },
|
|
2791
2838
|
{ id: "used-fields", title: "Every required dependency is read", outcome: "no-match", files: [{ path: "src/case.ts", source: EXAMPLE2.replace("return user.name;", "return user.name + user.email;") }, CONFIG2], focusPath: "src/case.ts", expectedCount: 0, public: true },
|
|
2792
2839
|
{ id: "scalar-helper", scenarioId: "local-scalar-helper", title: "Only the name crosses a local scalar helper call", outcome: "match", files: [{ path: "src/case.ts", source: HELPER_EXAMPLE }, CONFIG2], focusPath: "src/case.ts", expectedCount: 1, public: true, fixedFiles: [{ path: "src/case.ts", source: HELPER_EXAMPLE.replace("email: string;", "") }] },
|
|
2793
|
-
{ id: "helper-and-other-field", scenarioId: "local-scalar-helper", title: "The consumer still requires a separately read field", outcome: "no-match", files: [{ path: "src/case.ts", source: HELPER_EXAMPLE.replace("return formatName(user.name);", "return formatName(user.name) + user.email;") }, CONFIG2], focusPath: "src/case.ts", expectedCount: 0, public: true }
|
|
2840
|
+
{ id: "helper-and-other-field", scenarioId: "local-scalar-helper", title: "The consumer still requires a separately read field", outcome: "no-match", files: [{ path: "src/case.ts", source: HELPER_EXAMPLE.replace("return formatName(user.name);", "return formatName(user.name) + user.email;") }, CONFIG2], focusPath: "src/case.ts", expectedCount: 0, public: true },
|
|
2841
|
+
{ id: "unread-nullable-field", scenarioId: "nullable-private-field", title: "A required nullable field is never inspected", outcome: "match", files: [{ path: "src/case.ts", source: NULLABLE_EXAMPLE }, CONFIG2], focusPath: "src/case.ts", expectedCount: 1, public: true, fixedFiles: [{ path: "src/case.ts", source: NULLABLE_EXAMPLE.replace("email: string | null;", "") }] },
|
|
2842
|
+
{ id: "read-nullable-field", scenarioId: "nullable-private-field", title: "An inspected nullable field keeps its contract", outcome: "no-match", files: [{ path: "src/case.ts", source: NULLABLE_EXAMPLE.replace("return user.name;", "return user.email ?? user.name;") }, CONFIG2], focusPath: "src/case.ts", expectedCount: 0, public: true }
|
|
2794
2843
|
]
|
|
2795
2844
|
};
|
|
2796
|
-
function inlineFields(node) {
|
|
2845
|
+
function inlineFields(node, reads) {
|
|
2797
2846
|
const fields2 = /* @__PURE__ */ new Map();
|
|
2798
2847
|
for (const member3 of node.members) {
|
|
2799
|
-
if (!import_typescript8.default.isPropertySignature(member3) || !member3.name || !import_typescript8.default.isIdentifier(member3.name) && !import_typescript8.default.isStringLiteralLike(member3.name) || (member3.modifiers?.length ?? 0) > 1 || member3.modifiers?.some((modifier) => modifier.kind !== import_typescript8.default.SyntaxKind.ReadonlyKeyword)
|
|
2848
|
+
if (!import_typescript8.default.isPropertySignature(member3) || !member3.name || !import_typescript8.default.isIdentifier(member3.name) && !import_typescript8.default.isStringLiteralLike(member3.name) || (member3.modifiers?.length ?? 0) > 1 || member3.modifiers?.some((modifier) => modifier.kind !== import_typescript8.default.SyntaxKind.ReadonlyKeyword)) return null;
|
|
2800
2849
|
const key = member3.name.text;
|
|
2850
|
+
if (!primitiveKeyword(member3.type) && (member3.questionToken || reads.has(key) || !nullishPrimitiveUnion(member3.type))) return null;
|
|
2801
2851
|
if (fields2.has(key)) return null;
|
|
2802
2852
|
fields2.set(key, !member3.questionToken);
|
|
2803
2853
|
}
|
|
2804
2854
|
return fields2;
|
|
2805
2855
|
}
|
|
2856
|
+
function nullishPrimitiveUnion(node) {
|
|
2857
|
+
return node !== void 0 && import_typescript8.default.isUnionTypeNode(node) && node.types.length === 2 && node.types.some(primitiveKeyword) && node.types.some((part) => part.kind === import_typescript8.default.SyntaxKind.UndefinedKeyword || import_typescript8.default.isLiteralTypeNode(part) && part.literal.kind === import_typescript8.default.SyntaxKind.NullKeyword);
|
|
2858
|
+
}
|
|
2806
2859
|
var prefer_minimal_parameter_contract_default = createRule({
|
|
2807
2860
|
name: "prefer-minimal-parameter-contract",
|
|
2808
2861
|
documentation: PREFER_MINIMAL_PARAMETER_CONTRACT_DOCUMENTATION,
|
|
@@ -2834,7 +2887,7 @@ var prefer_minimal_parameter_contract_default = createRule({
|
|
|
2834
2887
|
const consumer = privateScalarConsumer(candidate2, context.sourceCode, services, exports2, true);
|
|
2835
2888
|
const annotation = consumer?.parameter.type;
|
|
2836
2889
|
if (!consumer || !annotation || !import_typescript8.default.isTypeLiteralNode(annotation)) continue;
|
|
2837
|
-
const fields2 = inlineFields(annotation);
|
|
2890
|
+
const fields2 = inlineFields(annotation, consumer.reads);
|
|
2838
2891
|
if (!fields2 || [...consumer.reads].some((key) => !fields2.has(key)) || ![...consumer.reads].some((key) => fields2.get(key))) continue;
|
|
2839
2892
|
const unused = [...fields2].filter(([key, required]) => required && !consumer.reads.has(key)).map(([key]) => key).sort();
|
|
2840
2893
|
if (!unused.length) continue;
|
|
@@ -3954,7 +4007,7 @@ function isSearchParamReceiver(call, services) {
|
|
|
3954
4007
|
// package.json
|
|
3955
4008
|
var package_default = {
|
|
3956
4009
|
name: "@sarj/eslint-plugin",
|
|
3957
|
-
version: "16.36.
|
|
4010
|
+
version: "16.36.5",
|
|
3958
4011
|
packageManager: "npm@12.2.1",
|
|
3959
4012
|
description: "Custom ESLint rules for hypermodern TypeScript / React / Next.js projects",
|
|
3960
4013
|
type: "module",
|
package/dist/index.js
CHANGED
|
@@ -1877,7 +1877,7 @@ var NO_TEST_COMMENTS_DOCUMENTATION = {
|
|
|
1877
1877
|
autofix: "safe",
|
|
1878
1878
|
limitations: [
|
|
1879
1879
|
"Checks actual line and block comment tokens in recognized maintained test files, including test fixtures; strings and template text remain unchanged. Generated/vendor files are excluded.",
|
|
1880
|
-
"Legal notices, shebangs, TypeScript triple-slash references, coverage/build annotations and explicit lint/type/test/formatter directives remain allowed.
|
|
1880
|
+
"Legal notices, shebangs, TypeScript triple-slash references, coverage/build annotations and explicit lint/type/test/formatter directives remain allowed. Executable detect-secrets 1.5.0 allowlist/whitelist pragmas retain its case-sensitive delimiter, spacing, physical-line and next-line grammar; arbitrary pragma prose remains ordinary commentary. Rationale on an executable directive remains the owning tool's responsibility.",
|
|
1881
1881
|
"The automatic fix removes comments and adjacent padding while retaining every line ending and necessary token boundary; it does not rename tests or remove executable statements. Bare integration/e2e directories and ambiguous hyphen/underscore test basenames require another concrete test-path signal or a runtime testing-framework import; ordinary production integration modules and rule implementations ending in -test are preserved."
|
|
1882
1882
|
],
|
|
1883
1883
|
examples: [
|
|
@@ -1888,6 +1888,52 @@ var NO_TEST_COMMENTS_DOCUMENTATION = {
|
|
|
1888
1888
|
var DIRECTIVE = /^(?:eslint(?:-\S+)?\b|(?:biome|oxlint|jshint|jslint|c8|v8|istanbul|vitest|prettier|dprint)\b|@(?:ts-(?:check|nocheck|expect-error|ignore)|vitest-environment|jest-environment|jsx\w*|license|preserve)\b|[#!]\s*__(?:PURE|NO_SIDE_EFFECTS)__|\/\s*<(?:reference|amd-module|amd-dependency)\b)/u;
|
|
1889
1889
|
var LEGAL = /(?:^|\n)\s*\*?\s*(?:Copyright\b|SPDX-License-Identifier:|Licensed under\b|@license\b|@preserve\b)/iu;
|
|
1890
1890
|
var TYPE_CONTRACT = /@(?:type|typedef|template|param|returns?|satisfies|callback|overload|implements|extends|augments|enum|this|import)\b/u;
|
|
1891
|
+
var DETECT_SECRETS_COMMENT_MARKERS = [
|
|
1892
|
+
{ start: "#", end: "" },
|
|
1893
|
+
{ start: "//", end: "" },
|
|
1894
|
+
{ start: "/\\*", end: " *\\*/" },
|
|
1895
|
+
{ start: "'", end: "" },
|
|
1896
|
+
{ start: "--", end: "" },
|
|
1897
|
+
{ start: "<!--[# \\t]*?", end: " *?-->" }
|
|
1898
|
+
];
|
|
1899
|
+
var DETECT_SECRETS_PRAGMAS = DETECT_SECRETS_COMMENT_MARKERS.flatMap(({ start, end }) => [
|
|
1900
|
+
{ nextLine: false, pattern: new RegExp(`[ \\t]*${start} *pragma: ?(?:allow|white)list[ -]secret[^\\n]*?${end}[ \\t]*(?![\\s\\S])`, "u") },
|
|
1901
|
+
{ nextLine: true, pattern: new RegExp(`^[ \\t]*${start} *pragma: ?allowlist[ -]nextline[ -]secret[^\\n]*?${end}[ \\t]*(?![\\s\\S])`, "u") }
|
|
1902
|
+
]);
|
|
1903
|
+
var PYTHON_WHITESPACE = new RegExp("\\p{White_Space}", "u");
|
|
1904
|
+
var PYTHON_EXTRA_WHITESPACE = /* @__PURE__ */ new Set(["", "", "", ""]);
|
|
1905
|
+
function hasDetectSecretsPragma(comment, text) {
|
|
1906
|
+
if (!comment.value.includes("pragma:")) return false;
|
|
1907
|
+
let lineStart = Math.max(text.lastIndexOf("\r", comment.range[0] - 1), text.lastIndexOf("\n", comment.range[0] - 1)) + 1;
|
|
1908
|
+
while (lineStart < comment.range[1]) {
|
|
1909
|
+
const nextCr = text.indexOf("\r", lineStart);
|
|
1910
|
+
const nextLf = text.indexOf("\n", lineStart);
|
|
1911
|
+
const lineEnd = Math.min(nextCr < 0 ? text.length : nextCr, nextLf < 0 ? text.length : nextLf);
|
|
1912
|
+
if (hasPragmaInReaderLine(comment, text.slice(lineStart, lineEnd), lineStart)) return true;
|
|
1913
|
+
lineStart = lineEnd + (text[lineEnd] === "\r" && text[lineEnd + 1] === "\n" ? 2 : 1);
|
|
1914
|
+
}
|
|
1915
|
+
return false;
|
|
1916
|
+
}
|
|
1917
|
+
function hasPragmaInReaderLine(comment, rawLine, lineStart) {
|
|
1918
|
+
const sameLine = stripPythonTrailingWhitespace(rawLine);
|
|
1919
|
+
for (const { nextLine, pattern } of DETECT_SECRETS_PRAGMAS) {
|
|
1920
|
+
const column = nextLine ? 0 : Math.max(0, comment.range[0] - lineStart);
|
|
1921
|
+
const match = pattern.exec((nextLine ? rawLine : sameLine).slice(column));
|
|
1922
|
+
if (match === null) continue;
|
|
1923
|
+
const pragmaStart = lineStart + column + match.index + match[0].indexOf("pragma:");
|
|
1924
|
+
if (pragmaStart >= comment.range[0] && pragmaStart < comment.range[1]) return true;
|
|
1925
|
+
}
|
|
1926
|
+
return false;
|
|
1927
|
+
}
|
|
1928
|
+
function stripPythonTrailingWhitespace(value) {
|
|
1929
|
+
let end = value.length;
|
|
1930
|
+
while (end > 0) {
|
|
1931
|
+
const character = value.charAt(end - 1);
|
|
1932
|
+
if (!PYTHON_WHITESPACE.test(character) && !PYTHON_EXTRA_WHITESPACE.has(character)) break;
|
|
1933
|
+
end--;
|
|
1934
|
+
}
|
|
1935
|
+
return value.slice(0, end);
|
|
1936
|
+
}
|
|
1891
1937
|
var no_test_comments_default = createRule({
|
|
1892
1938
|
name: "no-test-comments",
|
|
1893
1939
|
documentation: NO_TEST_COMMENTS_DOCUMENTATION,
|
|
@@ -1924,7 +1970,7 @@ var no_test_comments_default = createRule({
|
|
|
1924
1970
|
}
|
|
1925
1971
|
for (const [index, comment] of comments.entries()) {
|
|
1926
1972
|
const body3 = comment.value.trim().replace(/^\*\s*/u, "");
|
|
1927
|
-
if (DIRECTIVE.test(body3) || LEGAL.test(comment.value) || legalLines.has(index) || TYPE_CONTRACT.test(comment.value)) continue;
|
|
1973
|
+
if (DIRECTIVE.test(body3) || LEGAL.test(comment.value) || legalLines.has(index) || TYPE_CONTRACT.test(comment.value) || hasDetectSecretsPragma(comment, context.sourceCode.text)) continue;
|
|
1928
1974
|
context.report({
|
|
1929
1975
|
node: comment,
|
|
1930
1976
|
messageId: "testComment",
|
|
@@ -2722,6 +2768,7 @@ import ts8 from "typescript";
|
|
|
2722
2768
|
var CONFIG2 = { path: "tsconfig.json", source: '{"compilerOptions":{"strict":true,"target":"ESNext","module":"ESNext","moduleResolution":"Bundler"}}' };
|
|
2723
2769
|
var EXAMPLE2 = 'export {}; const account = {name: "Ada", email: "a"}; function label(user: {readonly email: string; readonly name: string}): string {return user.name;} label(account);';
|
|
2724
2770
|
var HELPER_EXAMPLE = 'export {}; const account = {name: "Ada", email: "a"}; function formatName(name: string): string {return name.toUpperCase();} function label(user: {email: string; name: string}): string {return formatName(user.name);} label(account);';
|
|
2771
|
+
var NULLABLE_EXAMPLE = 'export {}; const account = {name: "Ada", email: null}; function label(user: {email: string | null; name: string}): string {return user.name;} label(account);';
|
|
2725
2772
|
var PREFER_MINIMAL_PARAMETER_CONTRACT_DOCUMENTATION = {
|
|
2726
2773
|
defaultLevel: "warning",
|
|
2727
2774
|
summary: "Prefer the proven read-only subset for a private consumer parameter.",
|
|
@@ -2732,26 +2779,32 @@ var PREFER_MINIMAL_PARAMETER_CONTRACT_DOCUMENTATION = {
|
|
|
2732
2779
|
limitations: [
|
|
2733
2780
|
"Automatic fixes additionally require every existing identifier caller to be natively assignable to the original contract, without Any, unknown, error or unresolved types on the relevant surface. Comments inside the annotation and TypeScript suppression directives keep the finding manual. Only unread required property signatures are removed; every read or optional member, modifier and terminator token is preserved. Edits that would merge distinct CR and LF terminators remain manual.",
|
|
2734
2781
|
"Requires parser-owned TypeScript information and a strict external module. Only top-level private function declarations and raw const arrows with exactly one explicit parameter and an independent broad primitive return annotation are supported. Every callable reference must be a direct call with one identifier argument; exports including type-only exports, typeof queries, callbacks, storage, constructors, contextual signatures, overloads, generics, decorators, async functions and direct eval prevent findings.",
|
|
2735
|
-
"Only anonymous inline contracts made of broad string, number, boolean or bigint property types are supported. Fields may be plain or readonly; the consumer body must still prove read-only usage. Retained field modifiers and optional members are preserved. Named domain contracts, brands, literal discriminants, classes, methods, callbacks, indexes, mapped types and imported parameter contracts are excluded. At least one read required field and one unread required field must be established; all-optional reduced contracts can reject existing callers through weak-type checks.",
|
|
2782
|
+
"Only anonymous inline contracts made of broad string, number, boolean or bigint property types are supported. An unread required field may additionally have exactly one such keyword and null or undefined in a direct two-part union; read or optional nullable fields and more complex unions remain excluded. Fields may be plain or readonly; the consumer body must still prove read-only usage. Retained field modifiers and optional members are preserved. Named domain contracts, brands, literal discriminants, classes, methods, callbacks, indexes, mapped types and imported parameter contracts are excluded. At least one read required field and one unread required field must be established; all-optional reduced contracts can reject existing callers through weak-type checks.",
|
|
2736
2783
|
"The single return expression may use only fixed scalar field reads, flat destructuring, literals, operators, templates and scalar calls to same-file implemented top-level functions or raw const arrows with an explicit return annotation. These calls require one native resolved declaration, no generic or predicate signature, scalar arguments/result and complete active native checking of the local implementation file. Their helper bodies need not be pure because only scalar values cross the call and only an erased parameter annotation changes. Imported, aliased, ambient, overloaded, optional, spread and member calls remain unsupported. Bound destructured fields count as reads even when their local aliases are unused. Whole-object escape, identity, mutation, closures, reflection, serialization, dynamic keys, implicit Object capabilities and JSX are unsupported. Unknown semantic information causes abstention. Findings outside the fix proof remain manual; this is not a requirement to minimize every object or discard useful producer fields."
|
|
2737
2784
|
],
|
|
2738
2785
|
examples: [
|
|
2739
2786
|
{ id: "unused-required-field", title: "A private name consumer unnecessarily requires email", outcome: "match", files: [{ path: "src/case.ts", source: EXAMPLE2 }, CONFIG2], focusPath: "src/case.ts", expectedCount: 1, public: true, fixedFiles: [{ path: "src/case.ts", source: EXAMPLE2.replace("readonly email: string;", "") }] },
|
|
2740
2787
|
{ id: "used-fields", title: "Every required dependency is read", outcome: "no-match", files: [{ path: "src/case.ts", source: EXAMPLE2.replace("return user.name;", "return user.name + user.email;") }, CONFIG2], focusPath: "src/case.ts", expectedCount: 0, public: true },
|
|
2741
2788
|
{ id: "scalar-helper", scenarioId: "local-scalar-helper", title: "Only the name crosses a local scalar helper call", outcome: "match", files: [{ path: "src/case.ts", source: HELPER_EXAMPLE }, CONFIG2], focusPath: "src/case.ts", expectedCount: 1, public: true, fixedFiles: [{ path: "src/case.ts", source: HELPER_EXAMPLE.replace("email: string;", "") }] },
|
|
2742
|
-
{ id: "helper-and-other-field", scenarioId: "local-scalar-helper", title: "The consumer still requires a separately read field", outcome: "no-match", files: [{ path: "src/case.ts", source: HELPER_EXAMPLE.replace("return formatName(user.name);", "return formatName(user.name) + user.email;") }, CONFIG2], focusPath: "src/case.ts", expectedCount: 0, public: true }
|
|
2789
|
+
{ id: "helper-and-other-field", scenarioId: "local-scalar-helper", title: "The consumer still requires a separately read field", outcome: "no-match", files: [{ path: "src/case.ts", source: HELPER_EXAMPLE.replace("return formatName(user.name);", "return formatName(user.name) + user.email;") }, CONFIG2], focusPath: "src/case.ts", expectedCount: 0, public: true },
|
|
2790
|
+
{ id: "unread-nullable-field", scenarioId: "nullable-private-field", title: "A required nullable field is never inspected", outcome: "match", files: [{ path: "src/case.ts", source: NULLABLE_EXAMPLE }, CONFIG2], focusPath: "src/case.ts", expectedCount: 1, public: true, fixedFiles: [{ path: "src/case.ts", source: NULLABLE_EXAMPLE.replace("email: string | null;", "") }] },
|
|
2791
|
+
{ id: "read-nullable-field", scenarioId: "nullable-private-field", title: "An inspected nullable field keeps its contract", outcome: "no-match", files: [{ path: "src/case.ts", source: NULLABLE_EXAMPLE.replace("return user.name;", "return user.email ?? user.name;") }, CONFIG2], focusPath: "src/case.ts", expectedCount: 0, public: true }
|
|
2743
2792
|
]
|
|
2744
2793
|
};
|
|
2745
|
-
function inlineFields(node) {
|
|
2794
|
+
function inlineFields(node, reads) {
|
|
2746
2795
|
const fields2 = /* @__PURE__ */ new Map();
|
|
2747
2796
|
for (const member3 of node.members) {
|
|
2748
|
-
if (!ts8.isPropertySignature(member3) || !member3.name || !ts8.isIdentifier(member3.name) && !ts8.isStringLiteralLike(member3.name) || (member3.modifiers?.length ?? 0) > 1 || member3.modifiers?.some((modifier) => modifier.kind !== ts8.SyntaxKind.ReadonlyKeyword)
|
|
2797
|
+
if (!ts8.isPropertySignature(member3) || !member3.name || !ts8.isIdentifier(member3.name) && !ts8.isStringLiteralLike(member3.name) || (member3.modifiers?.length ?? 0) > 1 || member3.modifiers?.some((modifier) => modifier.kind !== ts8.SyntaxKind.ReadonlyKeyword)) return null;
|
|
2749
2798
|
const key = member3.name.text;
|
|
2799
|
+
if (!primitiveKeyword(member3.type) && (member3.questionToken || reads.has(key) || !nullishPrimitiveUnion(member3.type))) return null;
|
|
2750
2800
|
if (fields2.has(key)) return null;
|
|
2751
2801
|
fields2.set(key, !member3.questionToken);
|
|
2752
2802
|
}
|
|
2753
2803
|
return fields2;
|
|
2754
2804
|
}
|
|
2805
|
+
function nullishPrimitiveUnion(node) {
|
|
2806
|
+
return node !== void 0 && ts8.isUnionTypeNode(node) && node.types.length === 2 && node.types.some(primitiveKeyword) && node.types.some((part) => part.kind === ts8.SyntaxKind.UndefinedKeyword || ts8.isLiteralTypeNode(part) && part.literal.kind === ts8.SyntaxKind.NullKeyword);
|
|
2807
|
+
}
|
|
2755
2808
|
var prefer_minimal_parameter_contract_default = createRule({
|
|
2756
2809
|
name: "prefer-minimal-parameter-contract",
|
|
2757
2810
|
documentation: PREFER_MINIMAL_PARAMETER_CONTRACT_DOCUMENTATION,
|
|
@@ -2783,7 +2836,7 @@ var prefer_minimal_parameter_contract_default = createRule({
|
|
|
2783
2836
|
const consumer = privateScalarConsumer(candidate2, context.sourceCode, services, exports, true);
|
|
2784
2837
|
const annotation = consumer?.parameter.type;
|
|
2785
2838
|
if (!consumer || !annotation || !ts8.isTypeLiteralNode(annotation)) continue;
|
|
2786
|
-
const fields2 = inlineFields(annotation);
|
|
2839
|
+
const fields2 = inlineFields(annotation, consumer.reads);
|
|
2787
2840
|
if (!fields2 || [...consumer.reads].some((key) => !fields2.has(key)) || ![...consumer.reads].some((key) => fields2.get(key))) continue;
|
|
2788
2841
|
const unused = [...fields2].filter(([key, required]) => required && !consumer.reads.has(key)).map(([key]) => key).sort();
|
|
2789
2842
|
if (!unused.length) continue;
|
|
@@ -3907,7 +3960,7 @@ function isSearchParamReceiver(call, services) {
|
|
|
3907
3960
|
// package.json
|
|
3908
3961
|
var package_default = {
|
|
3909
3962
|
name: "@sarj/eslint-plugin",
|
|
3910
|
-
version: "16.36.
|
|
3963
|
+
version: "16.36.5",
|
|
3911
3964
|
packageManager: "npm@12.2.1",
|
|
3912
3965
|
description: "Custom ESLint rules for hypermodern TypeScript / React / Next.js projects",
|
|
3913
3966
|
type: "module",
|