@produtype/core 1.9.3 → 1.10.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.
@@ -127,6 +127,54 @@ function isCitableLine(line) {
127
127
  * on is not worth the one it displaces.
128
128
  */
129
129
  const MAX_CITABLE_LINE = 500;
130
+ /**
131
+ * A placeholder is not a value.
132
+ *
133
+ * pocketbase was reported as having tenant boundaries — `passed`, which raises a
134
+ * score — and the only two strong matches in its readable source were
135
+ * `"Ex. https://login.microsoftonline.com/YOUR_DIRECTORY_TENANT_ID/oauth2/v2.0/authorize"`,
136
+ * twice, in the help text of the form where somebody configures Microsoft sign-in.
137
+ * Microsoft Entra calls its directory a tenant; the string is telling a reader where
138
+ * to paste theirs. pocketbase has no organizations at all, and the rest of that
139
+ * finding was Apple's developer `teamId`, a weak word that cannot stand alone.
140
+ *
141
+ * `YOUR_SOMETHING` is the convention for "replace this", in documentation, in example
142
+ * configuration and in the help text beside a field. This analyzer already knows the
143
+ * shape: `your[_-]?secret` has been in the weak-secret list since the beginning.
144
+ *
145
+ * The token around the match is what decides, not the line. A line may hold a
146
+ * placeholder and a real value both, and only the matched one is being judged.
147
+ */
148
+ const PLACEHOLDER_TOKEN = /^(?:your|my|sample|example|placeholder|changeme|todo|xxx+)[_-]/i;
149
+ function insideAPlaceholder(line, index, length) {
150
+ let start = index;
151
+ while (start > 0 && /[A-Za-z0-9_-]/.test(line[start - 1]))
152
+ start--;
153
+ let end = index + length;
154
+ while (end < line.length && /[A-Za-z0-9_-]/.test(line[end]))
155
+ end++;
156
+ return PLACEHOLDER_TOKEN.test(line.slice(start, end));
157
+ }
158
+ /**
159
+ * Where a needle first matches, or -1. Shared so that both searches below judge a
160
+ * match the same way.
161
+ */
162
+ function findNeedle(line, needle) {
163
+ if (typeof needle === 'string') {
164
+ const index = line.indexOf(needle);
165
+ return index === -1 ? null : { index, length: needle.length };
166
+ }
167
+ const found = new RegExp(needle.source, needle.flags.replace('g', '')).exec(line);
168
+ return found ? { index: found.index, length: found[0].length } : null;
169
+ }
170
+ function matchesHere(line, needles) {
171
+ for (const needle of needles) {
172
+ const found = findNeedle(line, needle);
173
+ if (found && !insideAPlaceholder(line, found.index, found.length))
174
+ return true;
175
+ }
176
+ return false;
177
+ }
130
178
  /**
131
179
  * The lines of one already-read file that match, with the same hygiene the file
132
180
  * search applies: no comments, no pattern tables, no minified lines.
@@ -157,12 +205,8 @@ function matchLines(text, needles, file = '') {
157
205
  continue;
158
206
  if (declaresRatherThanDoes(line))
159
207
  continue;
160
- for (const n of needles) {
161
- const hit = typeof n === 'string' ? line.includes(n) : n.test(line);
162
- if (hit) {
163
- matches.push({ file, line: i + 1, snippet: line.trim().slice(0, 200) });
164
- break;
165
- }
208
+ if (matchesHere(line, needles)) {
209
+ matches.push({ file, line: i + 1, snippet: line.trim().slice(0, 200) });
166
210
  }
167
211
  }
168
212
  return matches;
@@ -205,14 +249,10 @@ async function searchInFiles(root, files, needles, limit = 25, keep) {
205
249
  continue;
206
250
  if (declaresRatherThanDoes(line))
207
251
  continue;
208
- for (const n of needles) {
209
- const hit = typeof n === 'string' ? line.includes(n) : n.test(line);
210
- if (hit) {
211
- const match = { file, line: i + 1, snippet: line.trim().slice(0, 200) };
212
- if (!keep || keep(match))
213
- matches.push(match);
214
- break;
215
- }
252
+ if (matchesHere(line, needles)) {
253
+ const match = { file, line: i + 1, snippet: line.trim().slice(0, 200) };
254
+ if (!keep || keep(match))
255
+ matches.push(match);
216
256
  }
217
257
  if (matches.length >= limit)
218
258
  break;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@produtype/core",
3
- "version": "1.9.3",
3
+ "version": "1.10.0",
4
4
  "description": "Deterministic CLI and library that analyzes a web application repository and reports how far it is from production-ready for the kind of product it is meant to be.",
5
5
  "license": "MIT",
6
6
  "bin": {