@sushichan044/kg-core 0.4.1 → 0.6.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/README.md +41 -4
- package/dist/index.d.mts +312 -255
- package/dist/index.mjs +404 -50
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -1298,7 +1298,11 @@ function interpolate(template, data) {
|
|
|
1298
1298
|
const ProofreadingReport = { schema: readonlyObject({
|
|
1299
1299
|
range: ManuscriptRange.schema,
|
|
1300
1300
|
messageId: v.string(),
|
|
1301
|
-
data: v.optional(v.pipe(v.record(v.string(), v.union([v.string(), v.number()])), v.readonly()))
|
|
1301
|
+
data: v.optional(v.pipe(v.record(v.string(), v.union([v.string(), v.number()])), v.readonly())),
|
|
1302
|
+
/**
|
|
1303
|
+
* Omitted means `error`: a rule that says nothing is claiming it found a real defect.
|
|
1304
|
+
*/
|
|
1305
|
+
severity: v.optional(DiagnosticSeverity.schema)
|
|
1302
1306
|
}) };
|
|
1303
1307
|
//#endregion
|
|
1304
1308
|
//#region src/proofreading/proofreading-rule.ts
|
|
@@ -1389,7 +1393,7 @@ function toDiagnostics({ id, messages }, reports, source) {
|
|
|
1389
1393
|
kind: "rule",
|
|
1390
1394
|
id
|
|
1391
1395
|
},
|
|
1392
|
-
severity: "error",
|
|
1396
|
+
severity: report.severity ?? "error",
|
|
1393
1397
|
code: report.messageId,
|
|
1394
1398
|
message: interpolate(template, report.data),
|
|
1395
1399
|
range: report.range
|
|
@@ -1509,8 +1513,9 @@ function defineParsedRule(id, message, check) {
|
|
|
1509
1513
|
messages: { [DEFAULT_MESSAGE_ID]: message }
|
|
1510
1514
|
},
|
|
1511
1515
|
check: (manuscript, context) => {
|
|
1512
|
-
check(manuscript, (range) => {
|
|
1516
|
+
check(manuscript, (range, options) => {
|
|
1513
1517
|
context.report({
|
|
1518
|
+
...options,
|
|
1514
1519
|
range,
|
|
1515
1520
|
messageId: DEFAULT_MESSAGE_ID
|
|
1516
1521
|
});
|
|
@@ -1538,12 +1543,194 @@ function defineMatchRule(options) {
|
|
|
1538
1543
|
});
|
|
1539
1544
|
}
|
|
1540
1545
|
//#endregion
|
|
1541
|
-
//#region src/proofreading/rules/
|
|
1542
|
-
const
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1546
|
+
//#region src/proofreading/rules/consistent-kanji-opening.ts
|
|
1547
|
+
const RULE_ID$5 = "kg/consistent-kanji-opening";
|
|
1548
|
+
const MESSAGE = "「{{ closed }}」と「{{ opened }}」が混在しています。文脈上の使い分けか表記ゆれかを確認してください";
|
|
1549
|
+
const KanjiOpeningPairSchema = v.pipe(readonlyObject({
|
|
1550
|
+
closed: v.pipe(v.string(), v.nonEmpty()),
|
|
1551
|
+
opened: v.pipe(v.string(), v.nonEmpty())
|
|
1552
|
+
}), v.check(({ closed, opened }) => closed !== opened, "a pair must hold two different spellings"));
|
|
1553
|
+
const PairsSchema = v.pipe(v.array(KanjiOpeningPairSchema), v.nonEmpty(), v.readonly());
|
|
1554
|
+
const DEFAULT_PAIRS = [
|
|
1555
|
+
{
|
|
1556
|
+
closed: "出来る",
|
|
1557
|
+
opened: "できる"
|
|
1558
|
+
},
|
|
1559
|
+
{
|
|
1560
|
+
closed: "出来ない",
|
|
1561
|
+
opened: "できない"
|
|
1562
|
+
},
|
|
1563
|
+
{
|
|
1564
|
+
closed: "下さい",
|
|
1565
|
+
opened: "ください"
|
|
1566
|
+
},
|
|
1567
|
+
{
|
|
1568
|
+
closed: "頂く",
|
|
1569
|
+
opened: "いただく"
|
|
1570
|
+
},
|
|
1571
|
+
{
|
|
1572
|
+
closed: "致す",
|
|
1573
|
+
opened: "いたす"
|
|
1574
|
+
},
|
|
1575
|
+
{
|
|
1576
|
+
closed: "何故",
|
|
1577
|
+
opened: "なぜ"
|
|
1578
|
+
},
|
|
1579
|
+
{
|
|
1580
|
+
closed: "為に",
|
|
1581
|
+
opened: "ために"
|
|
1582
|
+
},
|
|
1583
|
+
{
|
|
1584
|
+
closed: "時に",
|
|
1585
|
+
opened: "ときに"
|
|
1586
|
+
},
|
|
1587
|
+
{
|
|
1588
|
+
closed: "様に",
|
|
1589
|
+
opened: "ように"
|
|
1590
|
+
},
|
|
1591
|
+
{
|
|
1592
|
+
closed: "方が",
|
|
1593
|
+
opened: "ほうが"
|
|
1594
|
+
}
|
|
1595
|
+
];
|
|
1596
|
+
/**
|
|
1597
|
+
* Reports the first kanji occurrence of every pair the manuscript also writes in kana. Whether the
|
|
1598
|
+
* two spellings are a deliberate distinction is the author's call, so the finding is a warning.
|
|
1599
|
+
*/
|
|
1600
|
+
function build$3(pairs) {
|
|
1601
|
+
return defineParsedRule(RULE_ID$5, MESSAGE, (manuscript, report) => {
|
|
1602
|
+
for (const { closed, opened } of pairs) {
|
|
1603
|
+
if (!manuscript.displayText.includes(opened)) continue;
|
|
1604
|
+
const index = manuscript.displayText.indexOf(closed);
|
|
1605
|
+
if (index === -1) continue;
|
|
1606
|
+
report(displayRange(manuscript, index, index + closed.length), {
|
|
1607
|
+
data: {
|
|
1608
|
+
closed,
|
|
1609
|
+
opened
|
|
1610
|
+
},
|
|
1611
|
+
severity: "warning"
|
|
1612
|
+
});
|
|
1613
|
+
}
|
|
1614
|
+
});
|
|
1615
|
+
}
|
|
1616
|
+
const consistentKanjiOpeningRule = () => build$3(DEFAULT_PAIRS);
|
|
1617
|
+
function createConsistentKanjiOpeningRule(options = {}) {
|
|
1618
|
+
if (options.pairs === void 0) return ManuscriptResult.succeed(consistentKanjiOpeningRule());
|
|
1619
|
+
const pairs = v.safeParse(PairsSchema, options.pairs);
|
|
1620
|
+
if (!pairs.success) return ManuscriptResult.fail({
|
|
1621
|
+
kind: "InvalidRuleOptions",
|
|
1622
|
+
ruleId: RULE_ID$5,
|
|
1623
|
+
option: "pairs",
|
|
1624
|
+
issues: ValidationIssue.from(pairs.issues)
|
|
1625
|
+
});
|
|
1626
|
+
return ManuscriptResult.succeed(build$3(pairs.output));
|
|
1627
|
+
}
|
|
1628
|
+
//#endregion
|
|
1629
|
+
//#region src/proofreading/rules/internal/mixed-width.ts
|
|
1630
|
+
/**
|
|
1631
|
+
* Builds a rule that fires when a manuscript uses both widths of one character class. Which width
|
|
1632
|
+
* is right is the author's decision, so the finding is a warning, and one finding at the first
|
|
1633
|
+
* halfwidth occurrence says everything a list of every mixed character would.
|
|
1634
|
+
*/
|
|
1635
|
+
function defineMixedWidthRule({ id, message, halfwidth, fullwidth }) {
|
|
1636
|
+
return defineParsedRule(id, message, (manuscript, report) => {
|
|
1637
|
+
const found = halfwidth.exec(manuscript.displayText);
|
|
1638
|
+
const counterpart = fullwidth.exec(manuscript.displayText);
|
|
1639
|
+
if (found === null || counterpart === null) return;
|
|
1640
|
+
report(displayRange(manuscript, found.index, found.index + found[0].length), {
|
|
1641
|
+
data: {
|
|
1642
|
+
halfwidth: found[0],
|
|
1643
|
+
fullwidth: counterpart[0]
|
|
1644
|
+
},
|
|
1645
|
+
severity: "warning"
|
|
1646
|
+
});
|
|
1647
|
+
});
|
|
1648
|
+
}
|
|
1649
|
+
//#endregion
|
|
1650
|
+
//#region src/proofreading/rules/consistent-latin-width.ts
|
|
1651
|
+
const consistentLatinWidthRule = () => defineMixedWidthRule({
|
|
1652
|
+
id: "kg/consistent-latin-width",
|
|
1653
|
+
message: "半角英字「{{ halfwidth }}」と全角英字「{{ fullwidth }}」が混在しています。作品内の方針を確認してください",
|
|
1654
|
+
halfwidth: /[A-Za-z]/u,
|
|
1655
|
+
fullwidth: /[A-Za-z]/u
|
|
1656
|
+
});
|
|
1657
|
+
//#endregion
|
|
1658
|
+
//#region src/proofreading/rules/consistent-numeral-width.ts
|
|
1659
|
+
const consistentNumeralWidthRule = () => defineMixedWidthRule({
|
|
1660
|
+
id: "kg/consistent-numeral-width",
|
|
1661
|
+
message: "半角数字「{{ halfwidth }}」と全角数字「{{ fullwidth }}」が混在しています。作品内の方針を確認してください",
|
|
1662
|
+
halfwidth: /[0-9]/u,
|
|
1663
|
+
fullwidth: /[0-9]/u
|
|
1664
|
+
});
|
|
1665
|
+
//#endregion
|
|
1666
|
+
//#region src/proofreading/rules/dash.ts
|
|
1667
|
+
const RULE_ID$4 = "kg/dash";
|
|
1668
|
+
const DEFAULT_PREFERRED = "―";
|
|
1669
|
+
const DashCharacterSchema = v.picklist([
|
|
1670
|
+
"―",
|
|
1671
|
+
"—",
|
|
1672
|
+
"─"
|
|
1673
|
+
]);
|
|
1674
|
+
const DASH_LIKE_RUN = /[—–―─━﹣--]+/gu;
|
|
1675
|
+
const EVEN_COUNT_MESSAGE = "連続するダッシュの数は偶数にしてください";
|
|
1676
|
+
function messageIdFor(run, preferred) {
|
|
1677
|
+
if (run === "-") return void 0;
|
|
1678
|
+
if (run !== preferred.repeat(run.length)) return "character";
|
|
1679
|
+
return run.length % 2 === 1 ? "even-count" : void 0;
|
|
1680
|
+
}
|
|
1681
|
+
function build$2(preferred) {
|
|
1682
|
+
const messages = {
|
|
1683
|
+
character: `ダッシュには「${preferred}」を使ってください`,
|
|
1684
|
+
"even-count": EVEN_COUNT_MESSAGE
|
|
1685
|
+
};
|
|
1686
|
+
return {
|
|
1687
|
+
kind: "parsed",
|
|
1688
|
+
meta: {
|
|
1689
|
+
id: RULE_ID$4,
|
|
1690
|
+
messages
|
|
1691
|
+
},
|
|
1692
|
+
check: (manuscript, context) => {
|
|
1693
|
+
for (const line of splitDisplayLines(manuscript.displayText)) {
|
|
1694
|
+
DASH_LIKE_RUN.lastIndex = 0;
|
|
1695
|
+
let match = DASH_LIKE_RUN.exec(line.text);
|
|
1696
|
+
while (match !== null) {
|
|
1697
|
+
const run = match[0];
|
|
1698
|
+
const messageId = messageIdFor(run, preferred);
|
|
1699
|
+
if (messageId !== void 0) {
|
|
1700
|
+
const start = line.start + match.index;
|
|
1701
|
+
context.report({
|
|
1702
|
+
range: displayRange(manuscript, start, start + run.length),
|
|
1703
|
+
messageId
|
|
1704
|
+
});
|
|
1705
|
+
}
|
|
1706
|
+
match = DASH_LIKE_RUN.exec(line.text);
|
|
1707
|
+
}
|
|
1708
|
+
}
|
|
1709
|
+
}
|
|
1710
|
+
};
|
|
1711
|
+
}
|
|
1712
|
+
const dashRule = () => build$2(DEFAULT_PREFERRED);
|
|
1713
|
+
function createDashRule(options = {}) {
|
|
1714
|
+
if (options.preferred === void 0) return ManuscriptResult.succeed(dashRule());
|
|
1715
|
+
const preferred = v.safeParse(DashCharacterSchema, options.preferred);
|
|
1716
|
+
if (!preferred.success) return ManuscriptResult.fail({
|
|
1717
|
+
kind: "InvalidRuleOptions",
|
|
1718
|
+
ruleId: RULE_ID$4,
|
|
1719
|
+
option: "preferred",
|
|
1720
|
+
issues: ValidationIssue.from(preferred.issues)
|
|
1721
|
+
});
|
|
1722
|
+
return ManuscriptResult.succeed(build$2(preferred.output));
|
|
1723
|
+
}
|
|
1724
|
+
//#endregion
|
|
1725
|
+
//#region src/proofreading/rules/ellipsis-character.ts
|
|
1726
|
+
/**
|
|
1727
|
+
* Stand-ins for `…`. Runs of `。` and `・` are left to the rules that already count them, so one
|
|
1728
|
+
* substitute is never reported twice.
|
|
1729
|
+
*/
|
|
1730
|
+
const ellipsisCharacterRule = () => defineMatchRule({
|
|
1731
|
+
id: "kg/ellipsis-character",
|
|
1732
|
+
pattern: /\.{3,}|⋯+/gu,
|
|
1733
|
+
message: "三点リーダーには「…」を使ってください"
|
|
1547
1734
|
});
|
|
1548
1735
|
//#endregion
|
|
1549
1736
|
//#region src/proofreading/rules/even-ellipsis.ts
|
|
@@ -1554,13 +1741,78 @@ const evenEllipsisRule = () => defineMatchRule({
|
|
|
1554
1741
|
message: "連続する三点リーダーの数は偶数にしてください"
|
|
1555
1742
|
});
|
|
1556
1743
|
//#endregion
|
|
1744
|
+
//#region src/proofreading/rules/fullwidth-japanese-punctuation.ts
|
|
1745
|
+
const RULE_ID$3 = "kg/fullwidth-japanese-punctuation";
|
|
1746
|
+
const MESSAGES$2 = {
|
|
1747
|
+
"halfwidth-japanese-punctuation": "半角の「{{ character }}」があります。日本語の句読点・括弧・感嘆符・疑問符は全角にしてください",
|
|
1748
|
+
"halfwidth-near-japanese": "日本語の前後に半角の「{{ character }}」があります。全角にすべきか確認してください"
|
|
1749
|
+
};
|
|
1750
|
+
const JAPANESE_CHARACTER = /[\p{Script=Han}\p{Script=Hiragana}\p{Script=Katakana}々〆ヵヶ]/u;
|
|
1751
|
+
const CHECKS = [
|
|
1752
|
+
{
|
|
1753
|
+
pattern: /[。、「」]/gu,
|
|
1754
|
+
messageId: "halfwidth-japanese-punctuation",
|
|
1755
|
+
severity: "error",
|
|
1756
|
+
requiresJapanese: false
|
|
1757
|
+
},
|
|
1758
|
+
{
|
|
1759
|
+
pattern: /[!?]/gu,
|
|
1760
|
+
messageId: "halfwidth-japanese-punctuation",
|
|
1761
|
+
severity: "error",
|
|
1762
|
+
requiresJapanese: true
|
|
1763
|
+
},
|
|
1764
|
+
{
|
|
1765
|
+
pattern: /[,.()[\]{}]/gu,
|
|
1766
|
+
messageId: "halfwidth-near-japanese",
|
|
1767
|
+
severity: "warning",
|
|
1768
|
+
requiresJapanese: true
|
|
1769
|
+
}
|
|
1770
|
+
];
|
|
1771
|
+
function isBesideJapanese(text, index) {
|
|
1772
|
+
const before = text[index - 1];
|
|
1773
|
+
const after = text[index + 1];
|
|
1774
|
+
return before !== void 0 && JAPANESE_CHARACTER.test(before) || after !== void 0 && JAPANESE_CHARACTER.test(after);
|
|
1775
|
+
}
|
|
1776
|
+
/**
|
|
1777
|
+
* A period inside a run of three or more is a stand-in for `…`, which `kg/ellipsis-character`
|
|
1778
|
+
* already reports.
|
|
1779
|
+
*/
|
|
1780
|
+
function isEllipsisSubstitute(text, index) {
|
|
1781
|
+
if (text[index] !== ".") return false;
|
|
1782
|
+
let start = index;
|
|
1783
|
+
while (text[start - 1] === ".") start -= 1;
|
|
1784
|
+
let end = index;
|
|
1785
|
+
while (text[end + 1] === ".") end += 1;
|
|
1786
|
+
return end - start + 1 >= 3;
|
|
1787
|
+
}
|
|
1788
|
+
const fullwidthJapanesePunctuationRule = () => ({
|
|
1789
|
+
kind: "parsed",
|
|
1790
|
+
meta: {
|
|
1791
|
+
id: RULE_ID$3,
|
|
1792
|
+
messages: MESSAGES$2
|
|
1793
|
+
},
|
|
1794
|
+
check: (manuscript, context) => {
|
|
1795
|
+
for (const line of splitDisplayLines(manuscript.displayText)) for (const { pattern, messageId, severity, requiresJapanese } of CHECKS) for (const match of line.text.matchAll(pattern)) {
|
|
1796
|
+
if (requiresJapanese && !isBesideJapanese(line.text, match.index)) continue;
|
|
1797
|
+
if (isEllipsisSubstitute(line.text, match.index)) continue;
|
|
1798
|
+
const start = line.start + match.index;
|
|
1799
|
+
context.report({
|
|
1800
|
+
range: displayRange(manuscript, start, start + match[0].length),
|
|
1801
|
+
messageId,
|
|
1802
|
+
data: { character: match[0] },
|
|
1803
|
+
severity
|
|
1804
|
+
});
|
|
1805
|
+
}
|
|
1806
|
+
}
|
|
1807
|
+
});
|
|
1808
|
+
//#endregion
|
|
1557
1809
|
//#region src/proofreading/rules/max-arabic-numeral-digits.ts
|
|
1558
|
-
const RULE_ID$
|
|
1810
|
+
const RULE_ID$2 = "kg/max-arabic-numeral-digits";
|
|
1559
1811
|
const DEFAULT_MAX_DIGITS = 2;
|
|
1560
1812
|
const MaxDigitsSchema = v.pipe(v.number(), v.finite(), v.integer(), v.minValue(1));
|
|
1561
1813
|
function build$1(maxDigits) {
|
|
1562
1814
|
return defineMatchRule({
|
|
1563
|
-
id: RULE_ID$
|
|
1815
|
+
id: RULE_ID$2,
|
|
1564
1816
|
pattern: /([0-90-9]+)(?:[..]([0-90-9]+))?/gu,
|
|
1565
1817
|
accept: (match) => (match[1]?.length ?? 0) > maxDigits || (match[2]?.length ?? 0) > maxDigits,
|
|
1566
1818
|
message: `${maxDigits}桁を超えるアラビア数字が使われています`
|
|
@@ -1572,7 +1824,7 @@ function createMaxArabicNumeralDigitsRule(options = {}) {
|
|
|
1572
1824
|
const maxDigits = v.safeParse(MaxDigitsSchema, options.maxDigits);
|
|
1573
1825
|
if (!maxDigits.success) return ManuscriptResult.fail({
|
|
1574
1826
|
kind: "InvalidRuleOptions",
|
|
1575
|
-
ruleId: RULE_ID$
|
|
1827
|
+
ruleId: RULE_ID$2,
|
|
1576
1828
|
option: "maxDigits",
|
|
1577
1829
|
issues: ValidationIssue.from(maxDigits.issues)
|
|
1578
1830
|
});
|
|
@@ -1586,13 +1838,6 @@ const minusBeforeNumberRule = () => defineMatchRule({
|
|
|
1586
1838
|
message: "マイナス記号の直後には数字が必要です"
|
|
1587
1839
|
});
|
|
1588
1840
|
//#endregion
|
|
1589
|
-
//#region src/proofreading/rules/no-consecutive-choonpu.ts
|
|
1590
|
-
const noConsecutiveChoonpuRule = () => defineMatchRule({
|
|
1591
|
-
id: "kg/no-consecutive-choonpu",
|
|
1592
|
-
pattern: /ーー+/gu,
|
|
1593
|
-
message: "長音符が連続しています"
|
|
1594
|
-
});
|
|
1595
|
-
//#endregion
|
|
1596
1841
|
//#region src/proofreading/rules/no-consecutive-interpunct.ts
|
|
1597
1842
|
const noConsecutiveInterpunctRule = () => defineMatchRule({
|
|
1598
1843
|
id: "kg/no-consecutive-interpunct",
|
|
@@ -1607,46 +1852,151 @@ const noConsecutivePunctuationRule = () => defineMatchRule({
|
|
|
1607
1852
|
message: "句読点が連続しています"
|
|
1608
1853
|
});
|
|
1609
1854
|
//#endregion
|
|
1610
|
-
//#region src/proofreading/rules/
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1855
|
+
//#region src/proofreading/rules/internal/brackets.ts
|
|
1856
|
+
/**
|
|
1857
|
+
* Brackets and quotes a Japanese paragraph may open with. Halfwidth forms are included because a
|
|
1858
|
+
* manuscript that uses them is still opening a paragraph, not writing prose.
|
|
1859
|
+
*/
|
|
1860
|
+
const OPENING_BRACKETS = "「『〖〈《((“\"‘'[[〔{{<<";
|
|
1861
|
+
/**
|
|
1862
|
+
* The closing counterparts, in the same order.
|
|
1863
|
+
*/
|
|
1864
|
+
const CLOSING_BRACKETS = "」』〗〉》))”\"’']]〕}}>>";
|
|
1865
|
+
/**
|
|
1866
|
+
* Escapes the characters a regular expression character class would otherwise read as syntax, so a
|
|
1867
|
+
* set declared above can be dropped into a pattern.
|
|
1868
|
+
*/
|
|
1869
|
+
function characterClass(characters) {
|
|
1870
|
+
return characters.replaceAll(/[\\\]^-]/gu, "\\$&");
|
|
1871
|
+
}
|
|
1872
|
+
//#endregion
|
|
1873
|
+
//#region src/proofreading/rules/internal/decoration-line.ts
|
|
1874
|
+
const DECORATION_LINE = /^[**◆◇■□●○▽▼△▲☆★・…―─━ー\-==~~\s]+$/u;
|
|
1875
|
+
/**
|
|
1876
|
+
* A line made only of symbols and spaces: a scene break or a section marker rather than a
|
|
1877
|
+
* paragraph. Indentation rules skip these, because their layout is deliberate.
|
|
1878
|
+
*/
|
|
1879
|
+
function isDecorationLine(text) {
|
|
1880
|
+
return DECORATION_LINE.test(text);
|
|
1881
|
+
}
|
|
1882
|
+
//#endregion
|
|
1883
|
+
//#region src/proofreading/rules/internal/spaces.ts
|
|
1884
|
+
const LEADING_SPACES = /^[ \t]+/u;
|
|
1885
|
+
/**
|
|
1886
|
+
* The run of spaces the text begins with, or an empty string. Halfwidth spaces and tabs count: a
|
|
1887
|
+
* rule has to see them to say they are the wrong space.
|
|
1888
|
+
*/
|
|
1889
|
+
function leadingSpaces(text) {
|
|
1890
|
+
return LEADING_SPACES.exec(text)?.[0] ?? "";
|
|
1891
|
+
}
|
|
1892
|
+
//#endregion
|
|
1893
|
+
//#region src/proofreading/rules/paragraph-opening.ts
|
|
1894
|
+
const RULE_ID$1 = "kg/paragraph-opening";
|
|
1895
|
+
const MESSAGES$1 = {
|
|
1896
|
+
"leading-character": "段落の先頭には全角スペースまたは開き括弧が必要です",
|
|
1897
|
+
"indent-width": "段落冒頭の字下げは全角スペース1字にしてください",
|
|
1898
|
+
"unexpected-indent": "括弧から始まる段落を字下げすることはできません"
|
|
1899
|
+
};
|
|
1900
|
+
const OpeningBracketsSchema = v.pipe(v.string(), v.nonEmpty());
|
|
1901
|
+
/**
|
|
1902
|
+
* How a paragraph opens, judged as one decision: a paragraph that opens with a bracket carries no
|
|
1903
|
+
* indent, and prose carries exactly one ideographic space. The three findings are branches of that
|
|
1904
|
+
* one decision rather than separate rules, so a line can never collect two of them at once.
|
|
1905
|
+
*/
|
|
1906
|
+
function build(openingBrackets) {
|
|
1907
|
+
return {
|
|
1908
|
+
kind: "parsed",
|
|
1909
|
+
meta: {
|
|
1910
|
+
id: RULE_ID$1,
|
|
1911
|
+
messages: MESSAGES$1
|
|
1912
|
+
},
|
|
1913
|
+
check: (manuscript, context) => {
|
|
1914
|
+
const report = (start, end, messageId) => {
|
|
1915
|
+
context.report({
|
|
1916
|
+
range: displayRange(manuscript, start, end),
|
|
1917
|
+
messageId
|
|
1918
|
+
});
|
|
1919
|
+
};
|
|
1920
|
+
for (const line of splitDisplayLines(manuscript.displayText)) {
|
|
1921
|
+
if (isDecorationLine(line.text)) continue;
|
|
1922
|
+
const indent = leadingSpaces(line.text);
|
|
1923
|
+
const opening = line.text[indent.length];
|
|
1924
|
+
if (opening === void 0) continue;
|
|
1925
|
+
if (openingBrackets.includes(opening)) {
|
|
1926
|
+
if (indent !== "") report(line.start, line.start + indent.length, "unexpected-indent");
|
|
1927
|
+
} else if (!line.text.startsWith(" ")) {
|
|
1928
|
+
const length = (line.text.codePointAt(0) ?? 0) > 65535 ? 2 : 1;
|
|
1929
|
+
report(line.start, line.start + length, "leading-character");
|
|
1930
|
+
} else if (indent.length !== 1) report(line.start, line.start + indent.length, "indent-width");
|
|
1931
|
+
}
|
|
1622
1932
|
}
|
|
1623
|
-
}
|
|
1933
|
+
};
|
|
1624
1934
|
}
|
|
1625
|
-
const
|
|
1626
|
-
function
|
|
1627
|
-
if (options.
|
|
1628
|
-
const
|
|
1629
|
-
if (!
|
|
1935
|
+
const paragraphOpeningRule = () => build(OPENING_BRACKETS);
|
|
1936
|
+
function createParagraphOpeningRule(options = {}) {
|
|
1937
|
+
if (options.openingBrackets === void 0) return ManuscriptResult.succeed(paragraphOpeningRule());
|
|
1938
|
+
const brackets = v.safeParse(OpeningBracketsSchema, options.openingBrackets);
|
|
1939
|
+
if (!brackets.success) return ManuscriptResult.fail({
|
|
1630
1940
|
kind: "InvalidRuleOptions",
|
|
1631
|
-
ruleId: RULE_ID,
|
|
1632
|
-
option: "
|
|
1633
|
-
issues: ValidationIssue.from(
|
|
1941
|
+
ruleId: RULE_ID$1,
|
|
1942
|
+
option: "openingBrackets",
|
|
1943
|
+
issues: ValidationIssue.from(brackets.issues)
|
|
1634
1944
|
});
|
|
1635
|
-
return ManuscriptResult.succeed(build(
|
|
1945
|
+
return ManuscriptResult.succeed(build(brackets.output));
|
|
1636
1946
|
}
|
|
1637
1947
|
//#endregion
|
|
1638
1948
|
//#region src/proofreading/rules/punctuation-before-closing-quote.ts
|
|
1949
|
+
const PATTERN = new RegExp(`[。、]+(?=[${characterClass(CLOSING_BRACKETS)}])`, "gu");
|
|
1639
1950
|
const punctuationBeforeClosingQuoteRule = () => defineMatchRule({
|
|
1640
1951
|
id: "kg/punctuation-before-closing-quote",
|
|
1641
|
-
pattern:
|
|
1952
|
+
pattern: PATTERN,
|
|
1642
1953
|
message: "閉じ括弧の直前に句読点を置くことはできません"
|
|
1643
1954
|
});
|
|
1644
1955
|
//#endregion
|
|
1645
1956
|
//#region src/proofreading/rules/space-after-question-or-exclamation.ts
|
|
1646
|
-
const
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
|
|
1957
|
+
const RULE_ID = "kg/space-after-question-or-exclamation";
|
|
1958
|
+
const MESSAGES = {
|
|
1959
|
+
default: "感嘆符または疑問符の直後には全角スペースか閉じ括弧が必要です",
|
|
1960
|
+
"space-width": "感嘆符または疑問符の直後の空白は全角スペース1字にしてください",
|
|
1961
|
+
"before-closing-bracket": "閉じ括弧の直前に空白を置くことはできません"
|
|
1962
|
+
};
|
|
1963
|
+
/**
|
|
1964
|
+
* Runs, not single marks: `!?` is one mark and takes one gap, so the gap belongs after the run.
|
|
1965
|
+
*/
|
|
1966
|
+
const MARKS = /[?!]+/gu;
|
|
1967
|
+
/**
|
|
1968
|
+
* Checks the gap that follows every run of ! or ?. A run at the end of a line or right before a
|
|
1969
|
+
* closing bracket needs no gap; anywhere else it takes exactly one ideographic space.
|
|
1970
|
+
*/
|
|
1971
|
+
const spaceAfterQuestionOrExclamationRule = () => ({
|
|
1972
|
+
kind: "parsed",
|
|
1973
|
+
meta: {
|
|
1974
|
+
id: RULE_ID,
|
|
1975
|
+
messages: MESSAGES
|
|
1976
|
+
},
|
|
1977
|
+
check: (manuscript, context) => {
|
|
1978
|
+
const report = (start, end, messageId) => {
|
|
1979
|
+
context.report({
|
|
1980
|
+
range: displayRange(manuscript, start, end),
|
|
1981
|
+
messageId
|
|
1982
|
+
});
|
|
1983
|
+
};
|
|
1984
|
+
for (const line of splitDisplayLines(manuscript.displayText)) for (const marks of line.text.matchAll(MARKS)) {
|
|
1985
|
+
const marksEnd = marks.index + marks[0].length;
|
|
1986
|
+
const next = line.text[marksEnd];
|
|
1987
|
+
if (next === void 0 || "」』〗〉》))”\"’']]〕}}>>".includes(next)) continue;
|
|
1988
|
+
const spaces = leadingSpaces(line.text.slice(marksEnd));
|
|
1989
|
+
if (spaces === "") {
|
|
1990
|
+
report(line.start + marks.index, line.start + marksEnd, "default");
|
|
1991
|
+
continue;
|
|
1992
|
+
}
|
|
1993
|
+
const following = line.text[marksEnd + spaces.length];
|
|
1994
|
+
const spacesStart = line.start + marksEnd;
|
|
1995
|
+
const spacesEnd = spacesStart + spaces.length;
|
|
1996
|
+
if (following !== void 0 && "」』〗〉》))”\"’']]〕}}>>".includes(following)) report(spacesStart, spacesEnd, "before-closing-bracket");
|
|
1997
|
+
else if (spaces !== " ") report(spacesStart, spacesEnd, "space-width");
|
|
1998
|
+
}
|
|
1999
|
+
}
|
|
1650
2000
|
});
|
|
1651
2001
|
//#endregion
|
|
1652
2002
|
//#region src/proofreading/rules/variant-character.ts
|
|
@@ -1672,21 +2022,25 @@ const variantCharacterRule = () => defineParsedRule("kg/variant-character", "異
|
|
|
1672
2022
|
/**
|
|
1673
2023
|
* The built-in rule set at its default configuration. Infallible by construction: every rule is
|
|
1674
2024
|
* built from constants, so there is no options error for a caller to handle.
|
|
2025
|
+
*
|
|
2026
|
+
* Rules whose answer depends on the work's own conventions — which width numerals take, whether a
|
|
2027
|
+
* word is written in kanji or kana — are exported individually instead of included here.
|
|
1675
2028
|
*/
|
|
1676
2029
|
function createDefaultProofreadingRules() {
|
|
1677
2030
|
return [
|
|
1678
|
-
|
|
2031
|
+
paragraphOpeningRule(),
|
|
1679
2032
|
punctuationBeforeClosingQuoteRule(),
|
|
1680
2033
|
spaceAfterQuestionOrExclamationRule(),
|
|
1681
2034
|
evenEllipsisRule(),
|
|
1682
|
-
|
|
2035
|
+
dashRule(),
|
|
2036
|
+
ellipsisCharacterRule(),
|
|
1683
2037
|
noConsecutivePunctuationRule(),
|
|
1684
2038
|
noConsecutiveInterpunctRule(),
|
|
1685
|
-
noConsecutiveChoonpuRule(),
|
|
1686
2039
|
minusBeforeNumberRule(),
|
|
1687
2040
|
maxArabicNumeralDigitsRule(),
|
|
2041
|
+
fullwidthJapanesePunctuationRule(),
|
|
1688
2042
|
variantCharacterRule()
|
|
1689
2043
|
];
|
|
1690
2044
|
}
|
|
1691
2045
|
//#endregion
|
|
1692
|
-
export { BoldAnnotation, ComposeError, CompositionStatistics, DiagnosticOrigin, DiagnosticSeverity, DisplayRange, EmphasisAnnotation, FontPreset, FontPresetId, FontSizePt, GraphemeRange, GridCell, GridLine, GridPage, GridSettings, GridStage, InvalidRuleOptions, ItalicAnnotation, LineOffset, ManuscriptAnnotation, ManuscriptAppearanceSettings, ManuscriptCompositionSettings, ManuscriptDiagnostic, ManuscriptGeometry, ManuscriptGridLayout, ManuscriptOffsets, ManuscriptRange, ManuscriptResult, NamespacedId, PaperSize, PaperSizeId, ParseError, ParsedGrapheme, ParsedManuscript, ProofreadError, ProofreadingReport, ProofreadingRuleMeta, Rejection, RubyAnnotation, SourcePosition, SourceRange, TextRange, ValidationIssue, assertNever, composeManuscript, createDefaultProofreadingRules, createMaxArabicNumeralDigitsRule,
|
|
2046
|
+
export { BoldAnnotation, ComposeError, CompositionStatistics, DiagnosticOrigin, DiagnosticSeverity, DisplayRange, EmphasisAnnotation, FontPreset, FontPresetId, FontSizePt, GraphemeRange, GridCell, GridLine, GridPage, GridSettings, GridStage, InvalidRuleOptions, ItalicAnnotation, LineOffset, ManuscriptAnnotation, ManuscriptAppearanceSettings, ManuscriptCompositionSettings, ManuscriptDiagnostic, ManuscriptGeometry, ManuscriptGridLayout, ManuscriptOffsets, ManuscriptRange, ManuscriptResult, NamespacedId, PaperSize, PaperSizeId, ParseError, ParsedGrapheme, ParsedManuscript, ProofreadError, ProofreadingReport, ProofreadingRuleMeta, Rejection, RubyAnnotation, SourcePosition, SourceRange, TextRange, ValidationIssue, assertNever, composeManuscript, consistentKanjiOpeningRule, consistentLatinWidthRule, consistentNumeralWidthRule, createConsistentKanjiOpeningRule, createDashRule, createDefaultProofreadingRules, createMaxArabicNumeralDigitsRule, createParagraphOpeningRule, dashRule, ellipsisCharacterRule, evenEllipsisRule, fullwidthJapanesePunctuationRule, kakuyomuParser, manuscriptGridComposer, maxArabicNumeralDigitsRule, minusBeforeNumberRule, mmToPt, noConsecutiveInterpunctRule, noConsecutivePunctuationRule, paragraphOpeningRule, parseManuscript, pixivParser, plainTextParser, proofreadManuscript, ptToMm, punctuationBeforeClosingQuoteRule, spaceAfterQuestionOrExclamationRule, variantCharacterRule };
|