@casomoltd/tooling 0.40.0 → 0.41.0

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.
@@ -66,6 +66,9 @@ const FIELD_SUFFIX = /(?:value|val|text|node|display|content)$/;
66
66
  /** A key that names a birth. */
67
67
  const BIRTH_KEY = /(?:dob|dateofbirth|birthdate|born)$/;
68
68
 
69
+ /** How many nested field-name layers a lead-in may carry before the key. */
70
+ const MAX_FIELD_SUFFIXES = 4;
71
+
69
72
  /** The constructor a source file writes a date with. `new Date(1983, 6, 14)`
70
73
  * matches neither the printed nor the ISO rule, and the words before it end
71
74
  * `newdate` rather than a field name, so both halves would be blind at once
@@ -83,7 +86,7 @@ const DATE_CALL = /(?:new\s*)?date\($/;
83
86
  */
84
87
  function namesABirth(lead) {
85
88
  let rest = lead;
86
- for (let depth = 0; depth < 4; depth += 1) {
89
+ for (let depth = 0; depth < MAX_FIELD_SUFFIXES; depth += 1) {
87
90
  if (BIRTH_KEY.test(rest)) return true;
88
91
  const trimmed = rest.replace(FIELD_SUFFIX, "");
89
92
  if (trimmed === rest) return false;
@@ -94,11 +97,14 @@ function namesABirth(lead) {
94
97
 
95
98
  const NON_WORD = /[^a-z0-9]/gi;
96
99
 
97
- /** What precedes a date, as a reader sees it: tags dropped, a generous
98
- * window, because a label and its value are separated by a cell boundary
99
- * and whatever styling that cell carries. */
100
+ /** How far back a label may sit: generous, because a label and its value
101
+ * are separated by a cell boundary and whatever styling that cell carries. */
102
+ const LEAD_IN_CHARS = 120;
103
+
104
+ /** What precedes a date, as a reader sees it: tags dropped, within
105
+ * LEAD_IN_CHARS. */
100
106
  const leadIn = (before) =>
101
- before.replace(/<[^<>]*>/g, " ").slice(-120).replace(NON_WORD, "")
107
+ before.replace(/<[^<>]*>/g, " ").slice(-LEAD_IN_CHARS).replace(NON_WORD, "")
102
108
  .toLowerCase();
103
109
 
104
110
  /** A real address ends in a letters-only top-level domain; without this a
@@ -155,31 +161,34 @@ export function identityRules(allowEmail = []) {
155
161
 
156
162
  /** Every finding in one file's text, against the rules and the names. */
157
163
  export function findPersonalData(text, rules, names = []) {
158
- const hits = [];
159
164
  const lines = text.split("\n");
160
- for (const rule of rules) {
161
- for (const [i, line] of lines.entries()) {
162
- for (const m of line.matchAll(rule.re)) {
163
- if (rule.allow?.(m[0])) continue;
164
- if (rule.context && !rule.context(line.slice(0, m.index))) continue;
165
- hits.push({line: i + 1, rule: rule.name, text: m[0]});
166
- }
167
- }
168
- }
169
- for (const name of names) {
170
- // No `g`: `.test()` on a global regex advances `lastIndex`, and a name
171
- // on consecutive lines would be reported on every other one.
172
- const re = new RegExp(
173
- `\\b${name.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\b`, "i");
174
- for (const [i, line] of lines.entries()) {
175
- if (re.test(line)) {
176
- hits.push({line: i + 1, rule: "name on the local denylist", text: name});
177
- }
178
- }
179
- }
180
- return hits;
165
+ return [
166
+ ...rules.flatMap((rule) =>
167
+ lines.flatMap((line, i) => ruleHitsOnLine(rule, line, i + 1))),
168
+ ...names.flatMap((name) => nameHits(lines, name)),
169
+ ];
181
170
  }
182
171
 
172
+ /** One rule's findings on one line: each match its `allow` does not
173
+ * excuse and its `context`, if it has one, confirms. */
174
+ const ruleHitsOnLine = (rule, line, lineNumber) =>
175
+ [...line.matchAll(rule.re)]
176
+ .filter((m) => !rule.allow?.(m[0]))
177
+ .filter((m) => !rule.context || rule.context(line.slice(0, m.index)))
178
+ .map((m) => ({line: lineNumber, rule: rule.name, text: m[0]}));
179
+
180
+ /** Each line naming someone on the local denylist. */
181
+ const nameHits = (lines, name) => {
182
+ // No `g`: `.test()` on a global regex advances `lastIndex`, and a name on
183
+ // consecutive lines would be reported on every other one.
184
+ const re = new RegExp(
185
+ `\\b${name.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\b`, "i");
186
+ return lines.flatMap((line, i) =>
187
+ re.test(line)
188
+ ? [{line: i + 1, rule: "name on the local denylist", text: name}]
189
+ : []);
190
+ };
191
+
183
192
  /* Tracked AND untracked-but-not-ignored: the moment this exists for is the
184
193
  * first commit of a new directory, and a tracked-only listing is blind to
185
194
  * exactly that. Ignored files are left out on purpose; they are the working
@@ -263,6 +272,7 @@ const isEntryPoint = () => {
263
272
  try {
264
273
  return import.meta.url === pathToFileURL(realpathSync(process.argv[1])).href;
265
274
  } catch {
275
+ // No resolvable argv[1] (imported by a test, or a bare REPL): not the entry.
266
276
  return false;
267
277
  }
268
278
  };
@@ -25,6 +25,9 @@ export const CASOMO_ORGANIZATION = Object.freeze({
25
25
  "https://casomo.co.uk",
26
26
  "https://www.facebook.com/casomouk",
27
27
  "https://www.linkedin.com/company/111967567/",
28
+ // Where the company's open-source libraries are developed and published.
29
+ "https://github.com/casomoltd",
30
+ "https://www.npmjs.com/org/casomoltd",
28
31
  ]),
29
32
  });
30
33
 
package/package.json CHANGED
@@ -1,7 +1,8 @@
1
1
  {
2
2
  "name": "@casomoltd/tooling",
3
- "version": "0.40.0",
3
+ "version": "0.41.0",
4
4
  "description": "Casomo's shared dev config: ESLint, Prettier, commitlint, knip & jscpd presets, dev CLIs (git hooks, readability, screenshots, design specs), and the build-time data and helpers sites import (the Buttondown engine, the shared Organization)",
5
+ "author": "Casomo Ltd",
5
6
  "license": "MIT",
6
7
  "repository": {
7
8
  "type": "git",
@@ -108,10 +109,10 @@
108
109
  "typescript-eslint": "^8.72.0"
109
110
  },
110
111
  "overrides": {
111
- "minimatch": ">=11.0.1",
112
112
  "remark-lint-frontmatter-schema": {
113
113
  "@apidevtools/json-schema-ref-parser": "^11.1.1",
114
114
  "ajv": "^8.18.0",
115
+ "minimatch": "^9.0.7",
115
116
  "yaml": "^2.8.3"
116
117
  }
117
118
  }