prooflint 0.1.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.
- package/LICENSE +21 -0
- package/README.md +147 -0
- package/dist/cli.js +534 -0
- package/dist/cli.js.map +1 -0
- package/dist/index.cjs +491 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +418 -0
- package/dist/index.d.ts +418 -0
- package/dist/index.js +446 -0
- package/dist/index.js.map +1 -0
- package/package.json +65 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,446 @@
|
|
|
1
|
+
// src/rules/engine.ts
|
|
2
|
+
import { readFileSync } from "fs";
|
|
3
|
+
|
|
4
|
+
// src/parser/markdown.ts
|
|
5
|
+
import { remark } from "remark";
|
|
6
|
+
function parseMarkdown(content) {
|
|
7
|
+
const processor = remark();
|
|
8
|
+
const tree = processor.parse(content);
|
|
9
|
+
const textNodes = [];
|
|
10
|
+
const headings = [];
|
|
11
|
+
function visit(node) {
|
|
12
|
+
if (node.type === "heading") {
|
|
13
|
+
const text = extractText(node);
|
|
14
|
+
const position = node.position;
|
|
15
|
+
const textNode = {
|
|
16
|
+
text,
|
|
17
|
+
line: position?.start.line ?? 1,
|
|
18
|
+
column: position?.start.column ?? 1,
|
|
19
|
+
nodeType: "heading",
|
|
20
|
+
headingDepth: node.depth
|
|
21
|
+
};
|
|
22
|
+
headings.push(textNode);
|
|
23
|
+
textNodes.push(textNode);
|
|
24
|
+
} else if (node.type === "paragraph") {
|
|
25
|
+
const text = extractText(node);
|
|
26
|
+
const position = node.position;
|
|
27
|
+
textNodes.push({
|
|
28
|
+
text,
|
|
29
|
+
line: position?.start.line ?? 1,
|
|
30
|
+
column: position?.start.column ?? 1,
|
|
31
|
+
nodeType: "paragraph"
|
|
32
|
+
});
|
|
33
|
+
} else if (node.type === "listItem" || node.type === "blockquote") {
|
|
34
|
+
if ("children" in node) {
|
|
35
|
+
for (const child of node.children) {
|
|
36
|
+
visit(child);
|
|
37
|
+
}
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
if ("children" in node && node.type !== "heading" && node.type !== "paragraph") {
|
|
42
|
+
for (const child of node.children) {
|
|
43
|
+
visit(child);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
for (const child of tree.children) {
|
|
48
|
+
visit(child);
|
|
49
|
+
}
|
|
50
|
+
return { textNodes, headings, raw: content };
|
|
51
|
+
}
|
|
52
|
+
function extractText(node) {
|
|
53
|
+
if ("value" in node && typeof node.value === "string") {
|
|
54
|
+
return node.value;
|
|
55
|
+
}
|
|
56
|
+
if ("children" in node) {
|
|
57
|
+
return node.children.map(extractText).join("");
|
|
58
|
+
}
|
|
59
|
+
return "";
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// src/rules/pattern.ts
|
|
63
|
+
function applyPatternRule(rule, nodes) {
|
|
64
|
+
const messages = [];
|
|
65
|
+
for (const node of nodes) {
|
|
66
|
+
for (const pattern of rule.patterns) {
|
|
67
|
+
const flags = pattern.flags ?? "g";
|
|
68
|
+
let regex;
|
|
69
|
+
try {
|
|
70
|
+
regex = new RegExp(pattern.regex, flags.includes("g") ? flags : flags + "g");
|
|
71
|
+
} catch {
|
|
72
|
+
throw new Error(`Rule "${rule.id}": invalid regex "${pattern.regex}"`);
|
|
73
|
+
}
|
|
74
|
+
let match;
|
|
75
|
+
while ((match = regex.exec(node.text)) !== null) {
|
|
76
|
+
const beforeMatch = node.text.slice(0, match.index);
|
|
77
|
+
const newlines = (beforeMatch.match(/\n/g) ?? []).length;
|
|
78
|
+
const lastNewline = beforeMatch.lastIndexOf("\n");
|
|
79
|
+
const col = lastNewline === -1 ? node.column + match.index : match.index - lastNewline;
|
|
80
|
+
messages.push({
|
|
81
|
+
ruleId: rule.id,
|
|
82
|
+
severity: rule.severity,
|
|
83
|
+
message: pattern.message,
|
|
84
|
+
line: node.line + newlines,
|
|
85
|
+
column: col,
|
|
86
|
+
source: match[0]
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
return messages;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// src/rules/dictionary.ts
|
|
95
|
+
function isWordBoundary(text, start, end) {
|
|
96
|
+
const charBefore = start > 0 ? text[start - 1] : null;
|
|
97
|
+
const charAfter = end < text.length ? text[end] : null;
|
|
98
|
+
if (charAfter === "\u30FC" || charAfter === "\u301C") return false;
|
|
99
|
+
return true;
|
|
100
|
+
}
|
|
101
|
+
function applyDictionaryRule(rule, nodes) {
|
|
102
|
+
const messages = [];
|
|
103
|
+
for (const node of nodes) {
|
|
104
|
+
for (const term of rule.terms) {
|
|
105
|
+
for (const avoidWord of term.avoid) {
|
|
106
|
+
const preferStartsWithAvoid = term.prefer.startsWith(avoidWord);
|
|
107
|
+
const escaped = avoidWord.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
108
|
+
const regex = new RegExp(escaped, "g");
|
|
109
|
+
let match;
|
|
110
|
+
while ((match = regex.exec(node.text)) !== null) {
|
|
111
|
+
const matchStart = match.index;
|
|
112
|
+
const matchEnd = matchStart + avoidWord.length;
|
|
113
|
+
if (preferStartsWithAvoid && !isWordBoundary(node.text, matchStart, matchEnd)) {
|
|
114
|
+
continue;
|
|
115
|
+
}
|
|
116
|
+
const beforeMatch = node.text.slice(0, matchStart);
|
|
117
|
+
const newlines = (beforeMatch.match(/\n/g) ?? []).length;
|
|
118
|
+
const lastNewline = beforeMatch.lastIndexOf("\n");
|
|
119
|
+
const col = lastNewline === -1 ? node.column + matchStart : matchStart - lastNewline;
|
|
120
|
+
messages.push({
|
|
121
|
+
ruleId: rule.id,
|
|
122
|
+
severity: rule.severity,
|
|
123
|
+
message: `\u300C${avoidWord}\u300D\u2192\u300C${term.prefer}\u300D\u306B\u7D71\u4E00\u3057\u3066\u304F\u3060\u3055\u3044`,
|
|
124
|
+
line: node.line + newlines,
|
|
125
|
+
column: col,
|
|
126
|
+
source: match[0]
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
return messages;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// src/parser/sentence.ts
|
|
136
|
+
function splitSentences(text, startLine, startColumn) {
|
|
137
|
+
const sentences = [];
|
|
138
|
+
const lines = text.split("\n");
|
|
139
|
+
let currentLine = startLine;
|
|
140
|
+
let currentCol = startColumn;
|
|
141
|
+
let buffer = "";
|
|
142
|
+
let bufferLine = currentLine;
|
|
143
|
+
let bufferCol = currentCol;
|
|
144
|
+
for (let li = 0; li < lines.length; li++) {
|
|
145
|
+
const line = lines[li] ?? "";
|
|
146
|
+
let charPos = li === 0 ? startColumn - 1 : 0;
|
|
147
|
+
for (let ci = 0; ci < line.length; ci++) {
|
|
148
|
+
const ch = line[ci] ?? "";
|
|
149
|
+
buffer += ch;
|
|
150
|
+
charPos++;
|
|
151
|
+
if (isSentenceEnd(ch, line, ci)) {
|
|
152
|
+
const trimmed2 = buffer.trim();
|
|
153
|
+
if (trimmed2.length > 0) {
|
|
154
|
+
sentences.push({ text: trimmed2, line: bufferLine, column: bufferCol });
|
|
155
|
+
}
|
|
156
|
+
buffer = "";
|
|
157
|
+
bufferLine = currentLine;
|
|
158
|
+
bufferCol = charPos + 1;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
if (li < lines.length - 1) {
|
|
162
|
+
buffer += "\n";
|
|
163
|
+
currentLine++;
|
|
164
|
+
currentCol = 1;
|
|
165
|
+
if (buffer.trim() === "") {
|
|
166
|
+
bufferLine = currentLine;
|
|
167
|
+
bufferCol = 1;
|
|
168
|
+
buffer = "";
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
const trimmed = buffer.trim();
|
|
173
|
+
if (trimmed.length > 0) {
|
|
174
|
+
sentences.push({ text: trimmed, line: bufferLine, column: bufferCol });
|
|
175
|
+
}
|
|
176
|
+
return sentences;
|
|
177
|
+
}
|
|
178
|
+
function isSentenceEnd(ch, line, index) {
|
|
179
|
+
if ("\u3002\uFF01\uFF1F".includes(ch)) return true;
|
|
180
|
+
if (ch === "." || ch === "!" || ch === "?") {
|
|
181
|
+
const next = line[index + 1];
|
|
182
|
+
if (next === void 0 || next === " " || next === " ") return true;
|
|
183
|
+
}
|
|
184
|
+
return false;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// src/rules/structure.ts
|
|
188
|
+
function applyStructureRule(rule, nodes) {
|
|
189
|
+
const messages = [];
|
|
190
|
+
const target = rule.target ?? "sentence";
|
|
191
|
+
if (target === "heading") {
|
|
192
|
+
return applyHeadingRules(rule, nodes);
|
|
193
|
+
}
|
|
194
|
+
for (const node of nodes) {
|
|
195
|
+
if (node.nodeType === "heading") continue;
|
|
196
|
+
if (rule.max_chars !== void 0) {
|
|
197
|
+
const sentences = splitSentences(node.text, node.line, node.column);
|
|
198
|
+
for (const sentence of sentences) {
|
|
199
|
+
if (sentence.text.length > rule.max_chars) {
|
|
200
|
+
messages.push({
|
|
201
|
+
ruleId: rule.id,
|
|
202
|
+
severity: rule.severity,
|
|
203
|
+
message: `\u4E00\u6587\u304C${sentence.text.length}\u6587\u5B57\u3067\u3059\uFF08\u4E0A\u9650: ${rule.max_chars}\u6587\u5B57\uFF09`,
|
|
204
|
+
line: sentence.line,
|
|
205
|
+
column: sentence.column,
|
|
206
|
+
source: sentence.text.slice(0, 40) + (sentence.text.length > 40 ? "..." : "")
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
return messages;
|
|
213
|
+
}
|
|
214
|
+
function applyHeadingRules(rule, nodes) {
|
|
215
|
+
const messages = [];
|
|
216
|
+
for (const node of nodes) {
|
|
217
|
+
if (node.nodeType !== "heading") continue;
|
|
218
|
+
if (rule.max_level !== void 0 && node.headingDepth !== void 0) {
|
|
219
|
+
if (node.headingDepth > rule.max_level) {
|
|
220
|
+
messages.push({
|
|
221
|
+
ruleId: rule.id,
|
|
222
|
+
severity: rule.severity,
|
|
223
|
+
message: `\u898B\u51FA\u3057\u30EC\u30D9\u30EB${node.headingDepth}\u306F\u4E0A\u9650\uFF08H${rule.max_level}\uFF09\u3092\u8D85\u3048\u3066\u3044\u307E\u3059`,
|
|
224
|
+
line: node.line,
|
|
225
|
+
column: node.column,
|
|
226
|
+
source: node.text
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
if (rule.no_period === true) {
|
|
231
|
+
if (/[。..]$/.test(node.text.trim())) {
|
|
232
|
+
messages.push({
|
|
233
|
+
ruleId: rule.id,
|
|
234
|
+
severity: rule.severity,
|
|
235
|
+
message: "\u898B\u51FA\u3057\u306E\u672B\u5C3E\u306B\u53E5\u70B9\u3092\u4F7F\u308F\u306A\u3044\u3067\u304F\u3060\u3055\u3044",
|
|
236
|
+
line: node.line,
|
|
237
|
+
column: node.column,
|
|
238
|
+
source: node.text
|
|
239
|
+
});
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
return messages;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
// src/rules/engine.ts
|
|
247
|
+
function lintText(content, filePath, config) {
|
|
248
|
+
const doc = parseMarkdown(content);
|
|
249
|
+
const messages = [];
|
|
250
|
+
for (const rule of config.rules) {
|
|
251
|
+
switch (rule.type) {
|
|
252
|
+
case "pattern":
|
|
253
|
+
messages.push(...applyPatternRule(rule, doc.textNodes));
|
|
254
|
+
break;
|
|
255
|
+
case "dictionary":
|
|
256
|
+
messages.push(...applyDictionaryRule(rule, doc.textNodes));
|
|
257
|
+
break;
|
|
258
|
+
case "structure":
|
|
259
|
+
messages.push(...applyStructureRule(rule, doc.textNodes));
|
|
260
|
+
break;
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
messages.sort((a, b) => a.line !== b.line ? a.line - b.line : a.column - b.column);
|
|
264
|
+
return {
|
|
265
|
+
filePath,
|
|
266
|
+
messages,
|
|
267
|
+
errorCount: messages.filter((m) => m.severity === "error").length,
|
|
268
|
+
warningCount: messages.filter((m) => m.severity === "warning").length,
|
|
269
|
+
infoCount: messages.filter((m) => m.severity === "info").length
|
|
270
|
+
};
|
|
271
|
+
}
|
|
272
|
+
function lintFile(filePath, config) {
|
|
273
|
+
const content = readFileSync(filePath, "utf-8");
|
|
274
|
+
return lintText(content, filePath, config);
|
|
275
|
+
}
|
|
276
|
+
function lintFiles(filePaths, config) {
|
|
277
|
+
return filePaths.map((fp) => lintFile(fp, config));
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
// src/config/loader.ts
|
|
281
|
+
import { readFileSync as readFileSync2 } from "fs";
|
|
282
|
+
import { resolve, dirname } from "path";
|
|
283
|
+
import yaml from "js-yaml";
|
|
284
|
+
|
|
285
|
+
// src/rules/types.ts
|
|
286
|
+
import { z } from "zod";
|
|
287
|
+
var SeveritySchema = z.enum(["error", "warning", "info"]);
|
|
288
|
+
var PatternRuleSchema = z.object({
|
|
289
|
+
id: z.string().min(1),
|
|
290
|
+
type: z.literal("pattern"),
|
|
291
|
+
description: z.string().optional(),
|
|
292
|
+
severity: SeveritySchema.default("warning"),
|
|
293
|
+
patterns: z.array(
|
|
294
|
+
z.object({
|
|
295
|
+
regex: z.string().min(1),
|
|
296
|
+
message: z.string().min(1),
|
|
297
|
+
flags: z.string().optional()
|
|
298
|
+
})
|
|
299
|
+
).min(1)
|
|
300
|
+
});
|
|
301
|
+
var DictionaryRuleSchema = z.object({
|
|
302
|
+
id: z.string().min(1),
|
|
303
|
+
type: z.literal("dictionary"),
|
|
304
|
+
description: z.string().optional(),
|
|
305
|
+
severity: SeveritySchema.default("warning"),
|
|
306
|
+
terms: z.array(
|
|
307
|
+
z.object({
|
|
308
|
+
prefer: z.string().min(1),
|
|
309
|
+
avoid: z.array(z.string().min(1)).min(1)
|
|
310
|
+
})
|
|
311
|
+
).min(1)
|
|
312
|
+
});
|
|
313
|
+
var StructureRuleSchema = z.object({
|
|
314
|
+
id: z.string().min(1),
|
|
315
|
+
type: z.literal("structure"),
|
|
316
|
+
description: z.string().optional(),
|
|
317
|
+
severity: SeveritySchema.default("warning"),
|
|
318
|
+
target: z.enum(["sentence", "heading"]).default("sentence"),
|
|
319
|
+
max_chars: z.number().int().positive().optional(),
|
|
320
|
+
max_level: z.number().int().min(1).max(6).optional(),
|
|
321
|
+
no_period: z.boolean().optional()
|
|
322
|
+
});
|
|
323
|
+
var RuleSchema = z.discriminatedUnion("type", [
|
|
324
|
+
PatternRuleSchema,
|
|
325
|
+
DictionaryRuleSchema,
|
|
326
|
+
StructureRuleSchema
|
|
327
|
+
]);
|
|
328
|
+
var ContextSchema = z.object({
|
|
329
|
+
glossary: z.string().optional(),
|
|
330
|
+
style_guide: z.string().optional()
|
|
331
|
+
}).optional();
|
|
332
|
+
var ConfigSchema = z.object({
|
|
333
|
+
rules: z.array(RuleSchema).default([]),
|
|
334
|
+
context: ContextSchema
|
|
335
|
+
});
|
|
336
|
+
|
|
337
|
+
// src/config/loader.ts
|
|
338
|
+
var CONFIG_FILE_NAMES = [".prooflint.yml", ".prooflint.yaml", "prooflint.config.yml"];
|
|
339
|
+
function findConfigFile(cwd = process.cwd()) {
|
|
340
|
+
for (const name of CONFIG_FILE_NAMES) {
|
|
341
|
+
const candidate = resolve(cwd, name);
|
|
342
|
+
try {
|
|
343
|
+
readFileSync2(candidate);
|
|
344
|
+
return candidate;
|
|
345
|
+
} catch {
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
return null;
|
|
349
|
+
}
|
|
350
|
+
function loadConfig(configPath) {
|
|
351
|
+
const raw = readFileSync2(configPath, "utf-8");
|
|
352
|
+
const parsed = yaml.load(raw);
|
|
353
|
+
const result = ConfigSchema.safeParse(parsed);
|
|
354
|
+
if (!result.success) {
|
|
355
|
+
const issues = result.error.issues.map((issue) => ` - ${issue.path.join(".")}: ${issue.message}`).join("\n");
|
|
356
|
+
throw new Error(`Invalid config at ${configPath}:
|
|
357
|
+
${issues}`);
|
|
358
|
+
}
|
|
359
|
+
return result.data;
|
|
360
|
+
}
|
|
361
|
+
function loadConfigFromCwd(cwd = process.cwd()) {
|
|
362
|
+
const configPath = findConfigFile(cwd);
|
|
363
|
+
if (!configPath) {
|
|
364
|
+
return ConfigSchema.parse({});
|
|
365
|
+
}
|
|
366
|
+
return loadConfig(configPath);
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
// src/reporter/console.ts
|
|
370
|
+
import chalk from "chalk";
|
|
371
|
+
function severityLabel(severity) {
|
|
372
|
+
switch (severity) {
|
|
373
|
+
case "error":
|
|
374
|
+
return chalk.red("error");
|
|
375
|
+
case "warning":
|
|
376
|
+
return chalk.yellow("warn ");
|
|
377
|
+
case "info":
|
|
378
|
+
return chalk.blue("info ");
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
function formatMessage(msg) {
|
|
382
|
+
const location = chalk.dim(`${String(msg.line).padStart(4)}:${String(msg.column).padEnd(4)}`);
|
|
383
|
+
const sev = severityLabel(msg.severity);
|
|
384
|
+
const text = msg.message;
|
|
385
|
+
const rule = chalk.dim(msg.ruleId);
|
|
386
|
+
return ` ${location} ${sev} ${text} ${rule}`;
|
|
387
|
+
}
|
|
388
|
+
function formatConsole(results) {
|
|
389
|
+
const lines = [];
|
|
390
|
+
let totalErrors = 0;
|
|
391
|
+
let totalWarnings = 0;
|
|
392
|
+
let totalInfos = 0;
|
|
393
|
+
for (const result of results) {
|
|
394
|
+
if (result.messages.length === 0) continue;
|
|
395
|
+
lines.push("");
|
|
396
|
+
lines.push(chalk.underline(result.filePath));
|
|
397
|
+
for (const msg of result.messages) {
|
|
398
|
+
lines.push(formatMessage(msg));
|
|
399
|
+
}
|
|
400
|
+
totalErrors += result.errorCount;
|
|
401
|
+
totalWarnings += result.warningCount;
|
|
402
|
+
totalInfos += result.infoCount;
|
|
403
|
+
}
|
|
404
|
+
const total = totalErrors + totalWarnings + totalInfos;
|
|
405
|
+
if (total === 0) {
|
|
406
|
+
lines.push(chalk.green("\n0 problems"));
|
|
407
|
+
return lines.join("\n");
|
|
408
|
+
}
|
|
409
|
+
const parts = [];
|
|
410
|
+
if (totalErrors > 0) parts.push(chalk.red(`${totalErrors} error${totalErrors > 1 ? "s" : ""}`));
|
|
411
|
+
if (totalWarnings > 0) parts.push(chalk.yellow(`${totalWarnings} warning${totalWarnings > 1 ? "s" : ""}`));
|
|
412
|
+
if (totalInfos > 0) parts.push(chalk.blue(`${totalInfos} info`));
|
|
413
|
+
lines.push("");
|
|
414
|
+
lines.push(`${total} problem${total > 1 ? "s" : ""} (${parts.join(", ")})`);
|
|
415
|
+
return lines.join("\n");
|
|
416
|
+
}
|
|
417
|
+
function hasErrors(results) {
|
|
418
|
+
return results.some((r) => r.errorCount > 0);
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
// src/reporter/json.ts
|
|
422
|
+
function formatJson(results) {
|
|
423
|
+
const output = {
|
|
424
|
+
results,
|
|
425
|
+
summary: {
|
|
426
|
+
totalFiles: results.length,
|
|
427
|
+
filesWithProblems: results.filter((r) => r.messages.length > 0).length,
|
|
428
|
+
totalErrors: results.reduce((s, r) => s + r.errorCount, 0),
|
|
429
|
+
totalWarnings: results.reduce((s, r) => s + r.warningCount, 0),
|
|
430
|
+
totalInfos: results.reduce((s, r) => s + r.infoCount, 0)
|
|
431
|
+
}
|
|
432
|
+
};
|
|
433
|
+
return JSON.stringify(output, null, 2);
|
|
434
|
+
}
|
|
435
|
+
export {
|
|
436
|
+
findConfigFile,
|
|
437
|
+
formatConsole,
|
|
438
|
+
formatJson,
|
|
439
|
+
hasErrors,
|
|
440
|
+
lintFile,
|
|
441
|
+
lintFiles,
|
|
442
|
+
lintText,
|
|
443
|
+
loadConfig,
|
|
444
|
+
loadConfigFromCwd
|
|
445
|
+
};
|
|
446
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/rules/engine.ts","../src/parser/markdown.ts","../src/rules/pattern.ts","../src/rules/dictionary.ts","../src/parser/sentence.ts","../src/rules/structure.ts","../src/config/loader.ts","../src/rules/types.ts","../src/reporter/console.ts","../src/reporter/json.ts"],"sourcesContent":["import { readFileSync } from 'node:fs'\nimport { parseMarkdown } from '../parser/markdown.js'\nimport { applyPatternRule } from './pattern.js'\nimport { applyDictionaryRule } from './dictionary.js'\nimport { applyStructureRule } from './structure.js'\nimport type { Config, LintResult, LintMessage } from './types.js'\n\nexport function lintText(content: string, filePath: string, config: Config): LintResult {\n const doc = parseMarkdown(content)\n const messages: LintMessage[] = []\n\n for (const rule of config.rules) {\n switch (rule.type) {\n case 'pattern':\n messages.push(...applyPatternRule(rule, doc.textNodes))\n break\n case 'dictionary':\n messages.push(...applyDictionaryRule(rule, doc.textNodes))\n break\n case 'structure':\n messages.push(...applyStructureRule(rule, doc.textNodes))\n break\n }\n }\n\n // Sort by line then column\n messages.sort((a, b) => a.line !== b.line ? a.line - b.line : a.column - b.column)\n\n return {\n filePath,\n messages,\n errorCount: messages.filter((m) => m.severity === 'error').length,\n warningCount: messages.filter((m) => m.severity === 'warning').length,\n infoCount: messages.filter((m) => m.severity === 'info').length,\n }\n}\n\nexport function lintFile(filePath: string, config: Config): LintResult {\n const content = readFileSync(filePath, 'utf-8')\n return lintText(content, filePath, config)\n}\n\nexport function lintFiles(filePaths: string[], config: Config): LintResult[] {\n return filePaths.map((fp) => lintFile(fp, config))\n}\n","import { remark } from 'remark'\nimport type { Root, RootContent } from 'mdast'\n\nexport interface TextNode {\n text: string\n line: number\n column: number\n nodeType: 'paragraph' | 'heading' | 'listItem' | 'blockquote' | 'other'\n headingDepth?: number\n}\n\nexport interface ParsedDocument {\n textNodes: TextNode[]\n headings: TextNode[]\n raw: string\n}\n\nexport function parseMarkdown(content: string): ParsedDocument {\n const processor = remark()\n const tree = processor.parse(content) as Root\n const textNodes: TextNode[] = []\n const headings: TextNode[] = []\n\n function visit(node: RootContent): void {\n if (node.type === 'heading') {\n const text = extractText(node)\n const position = node.position\n const textNode: TextNode = {\n text,\n line: position?.start.line ?? 1,\n column: position?.start.column ?? 1,\n nodeType: 'heading',\n headingDepth: node.depth,\n }\n headings.push(textNode)\n textNodes.push(textNode)\n } else if (node.type === 'paragraph') {\n const text = extractText(node)\n const position = node.position\n textNodes.push({\n text,\n line: position?.start.line ?? 1,\n column: position?.start.column ?? 1,\n nodeType: 'paragraph',\n })\n } else if (node.type === 'listItem' || node.type === 'blockquote') {\n if ('children' in node) {\n for (const child of node.children) {\n visit(child as RootContent)\n }\n return\n }\n }\n\n if ('children' in node && node.type !== 'heading' && node.type !== 'paragraph') {\n for (const child of (node as { children: RootContent[] }).children) {\n visit(child)\n }\n }\n }\n\n for (const child of tree.children) {\n visit(child)\n }\n\n return { textNodes, headings, raw: content }\n}\n\nfunction extractText(node: RootContent): string {\n if ('value' in node && typeof node.value === 'string') {\n return node.value\n }\n if ('children' in node) {\n return (node as { children: RootContent[] }).children\n .map(extractText)\n .join('')\n }\n return ''\n}\n","import type { PatternRule, LintMessage } from './types.js'\nimport type { TextNode } from '../parser/markdown.js'\n\nexport function applyPatternRule(rule: PatternRule, nodes: TextNode[]): LintMessage[] {\n const messages: LintMessage[] = []\n\n for (const node of nodes) {\n for (const pattern of rule.patterns) {\n const flags = pattern.flags ?? 'g'\n let regex: RegExp\n try {\n regex = new RegExp(pattern.regex, flags.includes('g') ? flags : flags + 'g')\n } catch {\n throw new Error(`Rule \"${rule.id}\": invalid regex \"${pattern.regex}\"`)\n }\n\n let match: RegExpExecArray | null\n while ((match = regex.exec(node.text)) !== null) {\n const beforeMatch = node.text.slice(0, match.index)\n const newlines = (beforeMatch.match(/\\n/g) ?? []).length\n const lastNewline = beforeMatch.lastIndexOf('\\n')\n const col = lastNewline === -1\n ? node.column + match.index\n : match.index - lastNewline\n\n messages.push({\n ruleId: rule.id,\n severity: rule.severity,\n message: pattern.message,\n line: node.line + newlines,\n column: col,\n source: match[0],\n })\n }\n }\n }\n\n return messages\n}\n","import type { DictionaryRule, LintMessage } from './types.js'\nimport type { TextNode } from '../parser/markdown.js'\n\n// Check if the character at position is part of a word continuation\n// We want to avoid matching \"サーバ\" inside \"サーバー\"\nfunction isWordBoundary(text: string, start: number, end: number): boolean {\n const charBefore = start > 0 ? text[start - 1] : null\n const charAfter = end < text.length ? text[end] : null\n\n // If the character immediately after is a katakana long vowel mark (ー),\n // it means the \"avoid\" word is part of a longer preferred word\n if (charAfter === 'ー' || charAfter === '〜') return false\n\n return true\n}\n\nexport function applyDictionaryRule(rule: DictionaryRule, nodes: TextNode[]): LintMessage[] {\n const messages: LintMessage[] = []\n\n for (const node of nodes) {\n for (const term of rule.terms) {\n for (const avoidWord of term.avoid) {\n // Check that prefer doesn't start with avoidWord (avoid is a prefix of prefer)\n // e.g. \"サーバ\" is a prefix of \"サーバー\", so we need boundary checking\n const preferStartsWithAvoid = term.prefer.startsWith(avoidWord)\n\n const escaped = avoidWord.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&')\n const regex = new RegExp(escaped, 'g')\n\n let match: RegExpExecArray | null\n while ((match = regex.exec(node.text)) !== null) {\n const matchStart = match.index\n const matchEnd = matchStart + avoidWord.length\n\n // If prefer starts with avoid, check word boundary\n if (preferStartsWithAvoid && !isWordBoundary(node.text, matchStart, matchEnd)) {\n continue\n }\n\n const beforeMatch = node.text.slice(0, matchStart)\n const newlines = (beforeMatch.match(/\\n/g) ?? []).length\n const lastNewline = beforeMatch.lastIndexOf('\\n')\n const col = lastNewline === -1\n ? node.column + matchStart\n : matchStart - lastNewline\n\n messages.push({\n ruleId: rule.id,\n severity: rule.severity,\n message: `「${avoidWord}」→「${term.prefer}」に統一してください`,\n line: node.line + newlines,\n column: col,\n source: match[0],\n })\n }\n }\n }\n }\n\n return messages\n}\n","export interface Sentence {\n text: string\n line: number\n column: number\n}\n\n// Sentence delimiters for Japanese and Western text\nconst SENTENCE_END_PATTERN = /([。!?\\.\\!\\?]+)/g\n\n/**\n * Splits a block of text into individual sentences.\n * Handles Japanese sentence endings (。!?) and Western (. ! ?).\n * Preserves line/column tracking relative to the block's starting position.\n */\nexport function splitSentences(text: string, startLine: number, startColumn: number): Sentence[] {\n const sentences: Sentence[] = []\n const lines = text.split('\\n')\n\n let currentLine = startLine\n let currentCol = startColumn\n let buffer = ''\n let bufferLine = currentLine\n let bufferCol = currentCol\n\n for (let li = 0; li < lines.length; li++) {\n const line = lines[li] ?? ''\n let charPos = li === 0 ? startColumn - 1 : 0\n\n for (let ci = 0; ci < line.length; ci++) {\n const ch = line[ci] ?? ''\n buffer += ch\n charPos++\n\n if (isSentenceEnd(ch, line, ci)) {\n const trimmed = buffer.trim()\n if (trimmed.length > 0) {\n sentences.push({ text: trimmed, line: bufferLine, column: bufferCol })\n }\n buffer = ''\n bufferLine = currentLine\n bufferCol = charPos + 1\n }\n }\n\n if (li < lines.length - 1) {\n buffer += '\\n'\n currentLine++\n currentCol = 1\n if (buffer.trim() === '') {\n bufferLine = currentLine\n bufferCol = 1\n buffer = ''\n }\n }\n }\n\n const trimmed = buffer.trim()\n if (trimmed.length > 0) {\n sentences.push({ text: trimmed, line: bufferLine, column: bufferCol })\n }\n\n return sentences\n}\n\nfunction isSentenceEnd(ch: string, line: string, index: number): boolean {\n // Japanese terminators always end sentence\n if ('。!?'.includes(ch)) return true\n\n // Western period: end only if followed by space/end or next char is uppercase\n if (ch === '.' || ch === '!' || ch === '?') {\n const next = line[index + 1]\n if (next === undefined || next === ' ' || next === '\\t') return true\n }\n\n return false\n}\n","import type { StructureRule, LintMessage } from './types.js'\nimport type { TextNode } from '../parser/markdown.js'\nimport { splitSentences } from '../parser/sentence.js'\n\nexport function applyStructureRule(rule: StructureRule, nodes: TextNode[]): LintMessage[] {\n const messages: LintMessage[] = []\n const target = rule.target ?? 'sentence'\n\n if (target === 'heading') {\n return applyHeadingRules(rule, nodes)\n }\n\n // sentence target: check sentence length\n for (const node of nodes) {\n if (node.nodeType === 'heading') continue\n\n if (rule.max_chars !== undefined) {\n const sentences = splitSentences(node.text, node.line, node.column)\n for (const sentence of sentences) {\n if (sentence.text.length > rule.max_chars) {\n messages.push({\n ruleId: rule.id,\n severity: rule.severity,\n message: `一文が${sentence.text.length}文字です(上限: ${rule.max_chars}文字)`,\n line: sentence.line,\n column: sentence.column,\n source: sentence.text.slice(0, 40) + (sentence.text.length > 40 ? '...' : ''),\n })\n }\n }\n }\n }\n\n return messages\n}\n\nfunction applyHeadingRules(rule: StructureRule, nodes: TextNode[]): LintMessage[] {\n const messages: LintMessage[] = []\n\n for (const node of nodes) {\n if (node.nodeType !== 'heading') continue\n\n // Check heading level\n if (rule.max_level !== undefined && node.headingDepth !== undefined) {\n if (node.headingDepth > rule.max_level) {\n messages.push({\n ruleId: rule.id,\n severity: rule.severity,\n message: `見出しレベル${node.headingDepth}は上限(H${rule.max_level})を超えています`,\n line: node.line,\n column: node.column,\n source: node.text,\n })\n }\n }\n\n // Check trailing period in heading\n if (rule.no_period === true) {\n if (/[。..]$/.test(node.text.trim())) {\n messages.push({\n ruleId: rule.id,\n severity: rule.severity,\n message: '見出しの末尾に句点を使わないでください',\n line: node.line,\n column: node.column,\n source: node.text,\n })\n }\n }\n }\n\n return messages\n}\n","import { readFileSync } from 'node:fs'\nimport { resolve, dirname } from 'node:path'\nimport yaml from 'js-yaml'\nimport { ConfigSchema } from '../rules/types.js'\nimport type { Config } from '../rules/types.js'\n\nconst CONFIG_FILE_NAMES = ['.prooflint.yml', '.prooflint.yaml', 'prooflint.config.yml']\n\nexport function findConfigFile(cwd: string = process.cwd()): string | null {\n for (const name of CONFIG_FILE_NAMES) {\n const candidate = resolve(cwd, name)\n try {\n readFileSync(candidate)\n return candidate\n } catch {\n // not found, try next\n }\n }\n return null\n}\n\nexport function loadConfig(configPath: string): Config {\n const raw = readFileSync(configPath, 'utf-8')\n const parsed = yaml.load(raw)\n const result = ConfigSchema.safeParse(parsed)\n\n if (!result.success) {\n const issues = result.error.issues\n .map((issue) => ` - ${issue.path.join('.')}: ${issue.message}`)\n .join('\\n')\n throw new Error(`Invalid config at ${configPath}:\\n${issues}`)\n }\n\n return result.data\n}\n\nexport function loadConfigFromCwd(cwd: string = process.cwd()): Config {\n const configPath = findConfigFile(cwd)\n if (!configPath) {\n return ConfigSchema.parse({})\n }\n return loadConfig(configPath)\n}\n\nexport function resolveContextPaths(config: Config, configPath: string): Config {\n if (!config.context) return config\n\n const configDir = dirname(configPath)\n return {\n ...config,\n context: {\n glossary: config.context.glossary\n ? resolve(configDir, config.context.glossary)\n : undefined,\n style_guide: config.context.style_guide\n ? resolve(configDir, config.context.style_guide)\n : undefined,\n },\n }\n}\n","import { z } from 'zod'\n\n// Severity levels\nexport const SeveritySchema = z.enum(['error', 'warning', 'info'])\nexport type Severity = z.infer<typeof SeveritySchema>\n\n// Pattern rule: regex-based matching\nexport const PatternRuleSchema = z.object({\n id: z.string().min(1),\n type: z.literal('pattern'),\n description: z.string().optional(),\n severity: SeveritySchema.default('warning'),\n patterns: z.array(\n z.object({\n regex: z.string().min(1),\n message: z.string().min(1),\n flags: z.string().optional(),\n })\n ).min(1),\n})\nexport type PatternRule = z.infer<typeof PatternRuleSchema>\n\n// Dictionary rule: term consistency\nexport const DictionaryRuleSchema = z.object({\n id: z.string().min(1),\n type: z.literal('dictionary'),\n description: z.string().optional(),\n severity: SeveritySchema.default('warning'),\n terms: z.array(\n z.object({\n prefer: z.string().min(1),\n avoid: z.array(z.string().min(1)).min(1),\n })\n ).min(1),\n})\nexport type DictionaryRule = z.infer<typeof DictionaryRuleSchema>\n\n// Structure rule: sentence length, heading constraints\nexport const StructureRuleSchema = z.object({\n id: z.string().min(1),\n type: z.literal('structure'),\n description: z.string().optional(),\n severity: SeveritySchema.default('warning'),\n target: z.enum(['sentence', 'heading']).default('sentence'),\n max_chars: z.number().int().positive().optional(),\n max_level: z.number().int().min(1).max(6).optional(),\n no_period: z.boolean().optional(),\n})\nexport type StructureRule = z.infer<typeof StructureRuleSchema>\n\n// Union of all rule types\nexport const RuleSchema = z.discriminatedUnion('type', [\n PatternRuleSchema,\n DictionaryRuleSchema,\n StructureRuleSchema,\n])\nexport type Rule = z.infer<typeof RuleSchema>\n\n// Context (optional glossary/style guide files)\nexport const ContextSchema = z.object({\n glossary: z.string().optional(),\n style_guide: z.string().optional(),\n}).optional()\nexport type Context = z.infer<typeof ContextSchema>\n\n// Full config schema (.prooflint.yml)\nexport const ConfigSchema = z.object({\n rules: z.array(RuleSchema).default([]),\n context: ContextSchema,\n})\nexport type Config = z.infer<typeof ConfigSchema>\n\n// A single lint violation\nexport interface LintMessage {\n ruleId: string\n severity: Severity\n message: string\n line: number\n column: number\n source?: string\n}\n\n// Lint result for a single file\nexport interface LintResult {\n filePath: string\n messages: LintMessage[]\n errorCount: number\n warningCount: number\n infoCount: number\n}\n","import chalk from 'chalk'\nimport type { LintResult, LintMessage, Severity } from '../rules/types.js'\n\nfunction severityLabel(severity: Severity): string {\n switch (severity) {\n case 'error':\n return chalk.red('error')\n case 'warning':\n return chalk.yellow('warn ')\n case 'info':\n return chalk.blue('info ')\n }\n}\n\nfunction formatMessage(msg: LintMessage): string {\n const location = chalk.dim(`${String(msg.line).padStart(4)}:${String(msg.column).padEnd(4)}`)\n const sev = severityLabel(msg.severity)\n const text = msg.message\n const rule = chalk.dim(msg.ruleId)\n return ` ${location} ${sev} ${text} ${rule}`\n}\n\nexport function formatConsole(results: LintResult[]): string {\n const lines: string[] = []\n let totalErrors = 0\n let totalWarnings = 0\n let totalInfos = 0\n\n for (const result of results) {\n if (result.messages.length === 0) continue\n\n lines.push('')\n lines.push(chalk.underline(result.filePath))\n\n for (const msg of result.messages) {\n lines.push(formatMessage(msg))\n }\n\n totalErrors += result.errorCount\n totalWarnings += result.warningCount\n totalInfos += result.infoCount\n }\n\n const total = totalErrors + totalWarnings + totalInfos\n if (total === 0) {\n lines.push(chalk.green('\\n0 problems'))\n return lines.join('\\n')\n }\n\n const parts: string[] = []\n if (totalErrors > 0) parts.push(chalk.red(`${totalErrors} error${totalErrors > 1 ? 's' : ''}`))\n if (totalWarnings > 0) parts.push(chalk.yellow(`${totalWarnings} warning${totalWarnings > 1 ? 's' : ''}`))\n if (totalInfos > 0) parts.push(chalk.blue(`${totalInfos} info`))\n\n lines.push('')\n lines.push(`${total} problem${total > 1 ? 's' : ''} (${parts.join(', ')})`)\n\n return lines.join('\\n')\n}\n\nexport function hasErrors(results: LintResult[]): boolean {\n return results.some((r) => r.errorCount > 0)\n}\n","import type { LintResult } from '../rules/types.js'\n\nexport interface JsonOutput {\n results: LintResult[]\n summary: {\n totalFiles: number\n filesWithProblems: number\n totalErrors: number\n totalWarnings: number\n totalInfos: number\n }\n}\n\nexport function formatJson(results: LintResult[]): string {\n const output: JsonOutput = {\n results,\n summary: {\n totalFiles: results.length,\n filesWithProblems: results.filter((r) => r.messages.length > 0).length,\n totalErrors: results.reduce((s, r) => s + r.errorCount, 0),\n totalWarnings: results.reduce((s, r) => s + r.warningCount, 0),\n totalInfos: results.reduce((s, r) => s + r.infoCount, 0),\n },\n }\n return JSON.stringify(output, null, 2)\n}\n"],"mappings":"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","names":["trimmed","readFileSync","readFileSync"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "prooflint",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Declarative text linter for Japanese/Markdown — define rules in YAML, not JavaScript",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"lint",
|
|
7
|
+
"markdown",
|
|
8
|
+
"japanese",
|
|
9
|
+
"textlint",
|
|
10
|
+
"yaml"
|
|
11
|
+
],
|
|
12
|
+
"license": "MIT",
|
|
13
|
+
"author": "Higashiyama",
|
|
14
|
+
"repository": {
|
|
15
|
+
"type": "git",
|
|
16
|
+
"url": "git+https://github.com/higa4yama3/prooflint.git"
|
|
17
|
+
},
|
|
18
|
+
"homepage": "https://github.com/higa4yama3/prooflint#readme",
|
|
19
|
+
"type": "module",
|
|
20
|
+
"main": "./dist/index.cjs",
|
|
21
|
+
"module": "./dist/index.js",
|
|
22
|
+
"exports": {
|
|
23
|
+
".": {
|
|
24
|
+
"import": "./dist/index.js",
|
|
25
|
+
"require": "./dist/index.cjs"
|
|
26
|
+
}
|
|
27
|
+
},
|
|
28
|
+
"bin": {
|
|
29
|
+
"prooflint": "./dist/cli.js"
|
|
30
|
+
},
|
|
31
|
+
"files": [
|
|
32
|
+
"dist",
|
|
33
|
+
"README.md",
|
|
34
|
+
"LICENSE"
|
|
35
|
+
],
|
|
36
|
+
"scripts": {
|
|
37
|
+
"build": "tsup",
|
|
38
|
+
"dev": "tsup --watch",
|
|
39
|
+
"test": "vitest run",
|
|
40
|
+
"test:watch": "vitest",
|
|
41
|
+
"test:coverage": "vitest run --coverage",
|
|
42
|
+
"lint": "tsc --noEmit",
|
|
43
|
+
"prepublishOnly": "npm run build && npm test"
|
|
44
|
+
},
|
|
45
|
+
"devDependencies": {
|
|
46
|
+
"@types/js-yaml": "^4.0.9",
|
|
47
|
+
"@types/node": "^22.0.0",
|
|
48
|
+
"@vitest/coverage-v8": "^2.0.0",
|
|
49
|
+
"tsup": "^8.0.0",
|
|
50
|
+
"typescript": "^5.5.0",
|
|
51
|
+
"vitest": "^2.0.0"
|
|
52
|
+
},
|
|
53
|
+
"dependencies": {
|
|
54
|
+
"chalk": "^5.3.0",
|
|
55
|
+
"commander": "^12.0.0",
|
|
56
|
+
"js-yaml": "^4.1.0",
|
|
57
|
+
"remark": "^15.0.0",
|
|
58
|
+
"remark-parse": "^11.0.0",
|
|
59
|
+
"unified": "^11.0.0",
|
|
60
|
+
"zod": "^3.23.0"
|
|
61
|
+
},
|
|
62
|
+
"engines": {
|
|
63
|
+
"node": ">=18.0.0"
|
|
64
|
+
}
|
|
65
|
+
}
|