@sushichan044/kg-core 0.6.1 → 0.6.2
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.d.mts +248 -248
- package/dist/index.mjs +143 -133
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -1062,6 +1062,83 @@ const GridCell = {
|
|
|
1062
1062
|
annotations: []
|
|
1063
1063
|
})
|
|
1064
1064
|
};
|
|
1065
|
+
//#endregion
|
|
1066
|
+
//#region src/internal/japanese-brackets.ts
|
|
1067
|
+
/**
|
|
1068
|
+
* Brackets and quotes a Japanese paragraph may open with. Halfwidth forms are included because a
|
|
1069
|
+
* manuscript that uses them is still opening a paragraph, not writing prose.
|
|
1070
|
+
*/
|
|
1071
|
+
const OPENING_BRACKETS = "「『〖〈《((“\"‘'[[〔{{<<";
|
|
1072
|
+
/**
|
|
1073
|
+
* The closing counterparts, in the same order.
|
|
1074
|
+
*/
|
|
1075
|
+
const CLOSING_BRACKETS = "」』〗〉》))”\"’']]〕}}>>";
|
|
1076
|
+
/**
|
|
1077
|
+
* Escapes the characters a regular expression character class would otherwise read as syntax, so a
|
|
1078
|
+
* set declared above can be dropped into a pattern.
|
|
1079
|
+
*/
|
|
1080
|
+
function characterClass(characters) {
|
|
1081
|
+
return characters.replaceAll(/[\\\]^-]/gu, "\\$&");
|
|
1082
|
+
}
|
|
1083
|
+
//#endregion
|
|
1084
|
+
//#region src/internal/japanese-spaces.ts
|
|
1085
|
+
const LEADING_SPACES = /^[ \t]+/u;
|
|
1086
|
+
/**
|
|
1087
|
+
* The run of spaces the text begins with, or an empty string. Halfwidth spaces and tabs count: a
|
|
1088
|
+
* rule has to see them to say they are the wrong space.
|
|
1089
|
+
*/
|
|
1090
|
+
function leadingSpaces(text) {
|
|
1091
|
+
return LEADING_SPACES.exec(text)?.[0] ?? "";
|
|
1092
|
+
}
|
|
1093
|
+
//#endregion
|
|
1094
|
+
//#region src/internal/question-or-exclamation-spacing.ts
|
|
1095
|
+
/**
|
|
1096
|
+
* Runs, not single marks: `!?` is one mark and takes one gap, so the gap belongs after the run.
|
|
1097
|
+
*/
|
|
1098
|
+
const MARKS = /[?!]+/gu;
|
|
1099
|
+
function analyzeMatch(line, marks) {
|
|
1100
|
+
const start = marks.index;
|
|
1101
|
+
if (start === void 0) return void 0;
|
|
1102
|
+
const marksRange = {
|
|
1103
|
+
start,
|
|
1104
|
+
end: start + marks[0].length
|
|
1105
|
+
};
|
|
1106
|
+
const next = line[marksRange.end];
|
|
1107
|
+
if (next === void 0 || "」』〗〉》))”\"’']]〕}}>>".includes(next)) return void 0;
|
|
1108
|
+
const spaces = leadingSpaces(line.slice(marksRange.end));
|
|
1109
|
+
if (spaces === "") return {
|
|
1110
|
+
kind: "missing",
|
|
1111
|
+
marks: marksRange
|
|
1112
|
+
};
|
|
1113
|
+
const gap = {
|
|
1114
|
+
start: marksRange.end,
|
|
1115
|
+
end: marksRange.end + spaces.length
|
|
1116
|
+
};
|
|
1117
|
+
const following = line[gap.end];
|
|
1118
|
+
if (following !== void 0 && "」』〗〉》))”\"’']]〕}}>>".includes(following)) return {
|
|
1119
|
+
kind: "before-closing-bracket",
|
|
1120
|
+
marks: marksRange,
|
|
1121
|
+
gap
|
|
1122
|
+
};
|
|
1123
|
+
if (spaces !== " ") return {
|
|
1124
|
+
kind: "invalid-space",
|
|
1125
|
+
marks: marksRange,
|
|
1126
|
+
gap
|
|
1127
|
+
};
|
|
1128
|
+
return {
|
|
1129
|
+
kind: "valid",
|
|
1130
|
+
marks: marksRange,
|
|
1131
|
+
gap
|
|
1132
|
+
};
|
|
1133
|
+
}
|
|
1134
|
+
/**
|
|
1135
|
+
* Classifies the spacing after each run of fullwidth question or exclamation marks in one display
|
|
1136
|
+
* line. Runs at the end of the line or immediately before a closing bracket need no gap and are
|
|
1137
|
+
* omitted from the result.
|
|
1138
|
+
*/
|
|
1139
|
+
function questionOrExclamationSpacings(line) {
|
|
1140
|
+
return [...line.matchAll(MARKS)].map((marks) => analyzeMatch(line, marks)).filter((spacing) => spacing !== void 0);
|
|
1141
|
+
}
|
|
1065
1142
|
const GridLine = {
|
|
1066
1143
|
schema: readonlyObject({
|
|
1067
1144
|
range: v.nullable(ManuscriptRange.schema),
|
|
@@ -1191,12 +1268,32 @@ function toCells(sourceLine, annotations) {
|
|
|
1191
1268
|
annotations: ManuscriptAnnotation.overlapping(annotations, grapheme.range)
|
|
1192
1269
|
}));
|
|
1193
1270
|
}
|
|
1271
|
+
function hangingGapIndexes(sourceLine) {
|
|
1272
|
+
const line = sourceLine.map(({ value }) => value).join("");
|
|
1273
|
+
const gapStarts = new Set(questionOrExclamationSpacings(line).filter((spacing) => spacing.kind === "valid").map(({ gap }) => gap.start));
|
|
1274
|
+
const indexes = /* @__PURE__ */ new Set();
|
|
1275
|
+
let offset = 0;
|
|
1276
|
+
for (const [index, grapheme] of sourceLine.entries()) {
|
|
1277
|
+
if (gapStarts.has(offset)) indexes.add(index);
|
|
1278
|
+
offset += grapheme.value.length;
|
|
1279
|
+
}
|
|
1280
|
+
return indexes;
|
|
1281
|
+
}
|
|
1194
1282
|
/**
|
|
1195
|
-
* Wraps one source line across as many grid lines as its length requires.
|
|
1283
|
+
* Wraps one source line across as many grid lines as its length requires. A valid gap after ! or ?
|
|
1284
|
+
* that would start a wrapped line is consumed as hanging punctuation instead of occupying a cell.
|
|
1196
1285
|
*/
|
|
1197
|
-
function wrapLine(cells, charsPerLine) {
|
|
1286
|
+
function wrapLine(cells, charsPerLine, hangingGaps) {
|
|
1198
1287
|
if (cells.length === 0) return [GridLine.padded([], charsPerLine)];
|
|
1199
|
-
|
|
1288
|
+
const lines = [];
|
|
1289
|
+
let cursor = 0;
|
|
1290
|
+
while (cursor < cells.length) {
|
|
1291
|
+
if (lines.length > 0 && hangingGaps.has(cursor)) cursor += 1;
|
|
1292
|
+
if (cursor >= cells.length) break;
|
|
1293
|
+
lines.push(GridLine.padded(cells.slice(cursor, cursor + charsPerLine), charsPerLine));
|
|
1294
|
+
cursor += charsPerLine;
|
|
1295
|
+
}
|
|
1296
|
+
return lines;
|
|
1200
1297
|
}
|
|
1201
1298
|
function blankLines(count, charsPerLine) {
|
|
1202
1299
|
return Array.from({ length: count }, () => GridLine.padded([], charsPerLine));
|
|
@@ -1237,8 +1334,7 @@ function buildPages(contentLines, settings) {
|
|
|
1237
1334
|
function composeGrid(manuscript, settings) {
|
|
1238
1335
|
const { charsPerLine } = settings.grid;
|
|
1239
1336
|
const sourceLines = displayedLines(manuscript.graphemes);
|
|
1240
|
-
const
|
|
1241
|
-
const manuscriptLines = cellLines.flatMap((cells) => wrapLine(cells, charsPerLine));
|
|
1337
|
+
const manuscriptLines = sourceLines.flatMap((line) => wrapLine(toCells(line, manuscript.annotations), charsPerLine, hangingGapIndexes(line)));
|
|
1242
1338
|
const pages = buildPages([
|
|
1243
1339
|
...blankLines(settings.offsets.document.leading, charsPerLine),
|
|
1244
1340
|
...manuscriptLines,
|
|
@@ -1248,7 +1344,7 @@ function composeGrid(manuscript, settings) {
|
|
|
1248
1344
|
pages,
|
|
1249
1345
|
geometry: ManuscriptGeometry.of(settings.grid, settings.appearance),
|
|
1250
1346
|
stats: {
|
|
1251
|
-
chars:
|
|
1347
|
+
chars: sourceLines.reduce((total, line) => total + line.length, 0),
|
|
1252
1348
|
sourceLines: sourceLines.length,
|
|
1253
1349
|
pages: pages.length
|
|
1254
1350
|
}
|
|
@@ -1544,7 +1640,7 @@ function defineMatchRule(options) {
|
|
|
1544
1640
|
}
|
|
1545
1641
|
//#endregion
|
|
1546
1642
|
//#region src/proofreading/rules/consistent-kanji-opening.ts
|
|
1547
|
-
const RULE_ID$
|
|
1643
|
+
const RULE_ID$5 = "kg/consistent-kanji-opening";
|
|
1548
1644
|
const MESSAGE = "「{{ closed }}」と「{{ opened }}」が混在しています。文脈上の使い分けか表記ゆれかを確認してください";
|
|
1549
1645
|
const KanjiOpeningPairSchema = v.pipe(readonlyObject({
|
|
1550
1646
|
closed: v.pipe(v.string(), v.nonEmpty()),
|
|
@@ -1598,7 +1694,7 @@ const DEFAULT_PAIRS = [
|
|
|
1598
1694
|
* two spellings are a deliberate distinction is the author's call, so the finding is a warning.
|
|
1599
1695
|
*/
|
|
1600
1696
|
function build$3(pairs) {
|
|
1601
|
-
return defineParsedRule(RULE_ID$
|
|
1697
|
+
return defineParsedRule(RULE_ID$5, MESSAGE, (manuscript, report) => {
|
|
1602
1698
|
for (const { closed, opened } of pairs) {
|
|
1603
1699
|
if (!manuscript.displayText.includes(opened)) continue;
|
|
1604
1700
|
const index = manuscript.displayText.indexOf(closed);
|
|
@@ -1619,7 +1715,7 @@ function createConsistentKanjiOpeningRule(options = {}) {
|
|
|
1619
1715
|
const pairs = v.safeParse(PairsSchema, options.pairs);
|
|
1620
1716
|
if (!pairs.success) return ManuscriptResult.fail({
|
|
1621
1717
|
kind: "InvalidRuleOptions",
|
|
1622
|
-
ruleId: RULE_ID$
|
|
1718
|
+
ruleId: RULE_ID$5,
|
|
1623
1719
|
option: "pairs",
|
|
1624
1720
|
issues: ValidationIssue.from(pairs.issues)
|
|
1625
1721
|
});
|
|
@@ -1664,7 +1760,7 @@ const consistentNumeralWidthRule = () => defineMixedWidthRule({
|
|
|
1664
1760
|
});
|
|
1665
1761
|
//#endregion
|
|
1666
1762
|
//#region src/proofreading/rules/dash.ts
|
|
1667
|
-
const RULE_ID$
|
|
1763
|
+
const RULE_ID$4 = "kg/dash";
|
|
1668
1764
|
const DEFAULT_PREFERRED = "―";
|
|
1669
1765
|
const DashCharacterSchema = v.picklist([
|
|
1670
1766
|
"―",
|
|
@@ -1686,7 +1782,7 @@ function build$2(preferred) {
|
|
|
1686
1782
|
return {
|
|
1687
1783
|
kind: "parsed",
|
|
1688
1784
|
meta: {
|
|
1689
|
-
id: RULE_ID$
|
|
1785
|
+
id: RULE_ID$4,
|
|
1690
1786
|
messages
|
|
1691
1787
|
},
|
|
1692
1788
|
check: (manuscript, context) => {
|
|
@@ -1715,7 +1811,7 @@ function createDashRule(options = {}) {
|
|
|
1715
1811
|
const preferred = v.safeParse(DashCharacterSchema, options.preferred);
|
|
1716
1812
|
if (!preferred.success) return ManuscriptResult.fail({
|
|
1717
1813
|
kind: "InvalidRuleOptions",
|
|
1718
|
-
ruleId: RULE_ID$
|
|
1814
|
+
ruleId: RULE_ID$4,
|
|
1719
1815
|
option: "preferred",
|
|
1720
1816
|
issues: ValidationIssue.from(preferred.issues)
|
|
1721
1817
|
});
|
|
@@ -1742,8 +1838,8 @@ const evenEllipsisRule = () => defineMatchRule({
|
|
|
1742
1838
|
});
|
|
1743
1839
|
//#endregion
|
|
1744
1840
|
//#region src/proofreading/rules/fullwidth-japanese-punctuation.ts
|
|
1745
|
-
const RULE_ID$
|
|
1746
|
-
const MESSAGES$
|
|
1841
|
+
const RULE_ID$3 = "kg/fullwidth-japanese-punctuation";
|
|
1842
|
+
const MESSAGES$2 = {
|
|
1747
1843
|
"halfwidth-japanese-punctuation": "半角の「{{ character }}」があります。日本語の句読点・括弧・感嘆符・疑問符は全角にしてください",
|
|
1748
1844
|
"halfwidth-near-japanese": "日本語の前後に半角の「{{ character }}」があります。全角にすべきか確認してください"
|
|
1749
1845
|
};
|
|
@@ -1788,8 +1884,8 @@ function isEllipsisSubstitute(text, index) {
|
|
|
1788
1884
|
const fullwidthJapanesePunctuationRule = () => ({
|
|
1789
1885
|
kind: "parsed",
|
|
1790
1886
|
meta: {
|
|
1791
|
-
id: RULE_ID$
|
|
1792
|
-
messages: MESSAGES$
|
|
1887
|
+
id: RULE_ID$3,
|
|
1888
|
+
messages: MESSAGES$2
|
|
1793
1889
|
},
|
|
1794
1890
|
check: (manuscript, context) => {
|
|
1795
1891
|
for (const line of splitDisplayLines(manuscript.displayText)) for (const { pattern, messageId, severity, requiresJapanese } of CHECKS) for (const match of line.text.matchAll(pattern)) {
|
|
@@ -1807,12 +1903,12 @@ const fullwidthJapanesePunctuationRule = () => ({
|
|
|
1807
1903
|
});
|
|
1808
1904
|
//#endregion
|
|
1809
1905
|
//#region src/proofreading/rules/max-arabic-numeral-digits.ts
|
|
1810
|
-
const RULE_ID$
|
|
1906
|
+
const RULE_ID$2 = "kg/max-arabic-numeral-digits";
|
|
1811
1907
|
const DEFAULT_MAX_DIGITS = 2;
|
|
1812
1908
|
const MaxDigitsSchema = v.pipe(v.number(), v.finite(), v.integer(), v.minValue(1));
|
|
1813
1909
|
function build$1(maxDigits) {
|
|
1814
1910
|
return defineMatchRule({
|
|
1815
|
-
id: RULE_ID$
|
|
1911
|
+
id: RULE_ID$2,
|
|
1816
1912
|
pattern: /([0-90-9]+)(?:[..]([0-90-9]+))?/gu,
|
|
1817
1913
|
accept: (match) => (match[1]?.length ?? 0) > maxDigits || (match[2]?.length ?? 0) > maxDigits,
|
|
1818
1914
|
message: `${maxDigits}桁を超えるアラビア数字が使われています`
|
|
@@ -1824,7 +1920,7 @@ function createMaxArabicNumeralDigitsRule(options = {}) {
|
|
|
1824
1920
|
const maxDigits = v.safeParse(MaxDigitsSchema, options.maxDigits);
|
|
1825
1921
|
if (!maxDigits.success) return ManuscriptResult.fail({
|
|
1826
1922
|
kind: "InvalidRuleOptions",
|
|
1827
|
-
ruleId: RULE_ID$
|
|
1923
|
+
ruleId: RULE_ID$2,
|
|
1828
1924
|
option: "maxDigits",
|
|
1829
1925
|
issues: ValidationIssue.from(maxDigits.issues)
|
|
1830
1926
|
});
|
|
@@ -1852,24 +1948,6 @@ const noConsecutivePunctuationRule = () => defineMatchRule({
|
|
|
1852
1948
|
message: "句読点が連続しています"
|
|
1853
1949
|
});
|
|
1854
1950
|
//#endregion
|
|
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
1951
|
//#region src/proofreading/rules/internal/decoration-line.ts
|
|
1874
1952
|
const DECORATION_LINE = /^[**◆◇■□●○▽▼△▲☆★・…―─━ー\-==~~\s]+$/u;
|
|
1875
1953
|
/**
|
|
@@ -1880,19 +1958,9 @@ function isDecorationLine(text) {
|
|
|
1880
1958
|
return DECORATION_LINE.test(text);
|
|
1881
1959
|
}
|
|
1882
1960
|
//#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
1961
|
//#region src/proofreading/rules/paragraph-opening.ts
|
|
1894
|
-
const RULE_ID$
|
|
1895
|
-
const MESSAGES$
|
|
1962
|
+
const RULE_ID$1 = "kg/paragraph-opening";
|
|
1963
|
+
const MESSAGES$1 = {
|
|
1896
1964
|
"leading-character": "段落の先頭には全角スペースまたは開き括弧が必要です",
|
|
1897
1965
|
"indent-width": "段落冒頭の字下げは全角スペース1字にしてください",
|
|
1898
1966
|
"unexpected-indent": "括弧から始まる段落を字下げすることはできません"
|
|
@@ -1907,8 +1975,8 @@ function build(openingBrackets) {
|
|
|
1907
1975
|
return {
|
|
1908
1976
|
kind: "parsed",
|
|
1909
1977
|
meta: {
|
|
1910
|
-
id: RULE_ID$
|
|
1911
|
-
messages: MESSAGES$
|
|
1978
|
+
id: RULE_ID$1,
|
|
1979
|
+
messages: MESSAGES$1
|
|
1912
1980
|
},
|
|
1913
1981
|
check: (manuscript, context) => {
|
|
1914
1982
|
const report = (start, end, messageId) => {
|
|
@@ -1938,7 +2006,7 @@ function createParagraphOpeningRule(options = {}) {
|
|
|
1938
2006
|
const brackets = v.safeParse(OpeningBracketsSchema, options.openingBrackets);
|
|
1939
2007
|
if (!brackets.success) return ManuscriptResult.fail({
|
|
1940
2008
|
kind: "InvalidRuleOptions",
|
|
1941
|
-
ruleId: RULE_ID$
|
|
2009
|
+
ruleId: RULE_ID$1,
|
|
1942
2010
|
option: "openingBrackets",
|
|
1943
2011
|
issues: ValidationIssue.from(brackets.issues)
|
|
1944
2012
|
});
|
|
@@ -1954,25 +2022,21 @@ const punctuationBeforeClosingQuoteRule = () => defineMatchRule({
|
|
|
1954
2022
|
});
|
|
1955
2023
|
//#endregion
|
|
1956
2024
|
//#region src/proofreading/rules/space-after-question-or-exclamation.ts
|
|
1957
|
-
const RULE_ID
|
|
1958
|
-
const MESSAGES
|
|
2025
|
+
const RULE_ID = "kg/space-after-question-or-exclamation";
|
|
2026
|
+
const MESSAGES = {
|
|
1959
2027
|
default: "感嘆符または疑問符の直後には全角スペースか閉じ括弧が必要です",
|
|
1960
2028
|
"space-width": "感嘆符または疑問符の直後の空白は全角スペース1字にしてください",
|
|
1961
2029
|
"before-closing-bracket": "閉じ括弧の直前に空白を置くことはできません"
|
|
1962
2030
|
};
|
|
1963
2031
|
/**
|
|
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
2032
|
* Checks the gap that follows every run of ! or ?. A run at the end of a line or right before a
|
|
1969
2033
|
* closing bracket needs no gap; anywhere else it takes exactly one ideographic space.
|
|
1970
2034
|
*/
|
|
1971
2035
|
const spaceAfterQuestionOrExclamationRule = () => ({
|
|
1972
2036
|
kind: "parsed",
|
|
1973
2037
|
meta: {
|
|
1974
|
-
id: RULE_ID
|
|
1975
|
-
messages: MESSAGES
|
|
2038
|
+
id: RULE_ID,
|
|
2039
|
+
messages: MESSAGES
|
|
1976
2040
|
},
|
|
1977
2041
|
check: (manuscript, context) => {
|
|
1978
2042
|
const report = (start, end, messageId) => {
|
|
@@ -1981,93 +2045,39 @@ const spaceAfterQuestionOrExclamationRule = () => ({
|
|
|
1981
2045
|
messageId
|
|
1982
2046
|
});
|
|
1983
2047
|
};
|
|
1984
|
-
for (const line of splitDisplayLines(manuscript.displayText)) for (const
|
|
1985
|
-
|
|
1986
|
-
|
|
1987
|
-
|
|
1988
|
-
|
|
1989
|
-
|
|
1990
|
-
report(line.start +
|
|
1991
|
-
|
|
1992
|
-
|
|
1993
|
-
|
|
1994
|
-
|
|
1995
|
-
|
|
1996
|
-
if (following !== void 0 && "」』〗〉》))”\"’']]〕}}>>".includes(following)) report(spacesStart, spacesEnd, "before-closing-bracket");
|
|
1997
|
-
else if (spaces !== " ") report(spacesStart, spacesEnd, "space-width");
|
|
2048
|
+
for (const line of splitDisplayLines(manuscript.displayText)) for (const spacing of questionOrExclamationSpacings(line.text)) switch (spacing.kind) {
|
|
2049
|
+
case "valid": break;
|
|
2050
|
+
case "missing":
|
|
2051
|
+
report(line.start + spacing.marks.start, line.start + spacing.marks.end, "default");
|
|
2052
|
+
break;
|
|
2053
|
+
case "invalid-space":
|
|
2054
|
+
report(line.start + spacing.gap.start, line.start + spacing.gap.end, "space-width");
|
|
2055
|
+
break;
|
|
2056
|
+
case "before-closing-bracket":
|
|
2057
|
+
report(line.start + spacing.gap.start, line.start + spacing.gap.end, "before-closing-bracket");
|
|
2058
|
+
break;
|
|
2059
|
+
default: assertNever(spacing);
|
|
1998
2060
|
}
|
|
1999
2061
|
}
|
|
2000
2062
|
});
|
|
2001
2063
|
//#endregion
|
|
2002
2064
|
//#region src/proofreading/rules/variant-character.ts
|
|
2003
|
-
const RULE_ID = "kg/variant-character";
|
|
2004
|
-
const MESSAGES = {
|
|
2005
|
-
default: "異体字または字形選択子が使われています",
|
|
2006
|
-
"with-suggestion": "異体字または字形選択子が使われています。「{{ suggestion }}」ではありませんか?"
|
|
2007
|
-
};
|
|
2008
|
-
/**
|
|
2009
|
-
* Mongolian free variation selectors, variation selectors (incl. emoji presentation), IVSes: all
|
|
2010
|
-
* combine with a preceding base character rather than standing on their own.
|
|
2011
|
-
*/
|
|
2012
|
-
function isSelector(codePoint) {
|
|
2013
|
-
return codePoint >= 6155 && codePoint <= 6157 || codePoint === 6159 || codePoint >= 65024 && codePoint <= 65039 || codePoint >= 917760 && codePoint <= 917999;
|
|
2014
|
-
}
|
|
2015
2065
|
/**
|
|
2016
|
-
*
|
|
2066
|
+
* Mongolian free variation selectors, variation selectors, CJK compatibility ideographs, IVSes.
|
|
2017
2067
|
*/
|
|
2018
|
-
function isCompatibilityIdeograph(codePoint) {
|
|
2019
|
-
return codePoint >= 63744 && codePoint <= 64255 || codePoint >= 194560 && codePoint <= 195103;
|
|
2020
|
-
}
|
|
2021
2068
|
function isVariantCharacter(character) {
|
|
2022
2069
|
const codePoint = character.codePointAt(0);
|
|
2023
2070
|
if (codePoint === void 0) return false;
|
|
2024
|
-
return
|
|
2025
|
-
}
|
|
2026
|
-
/**
|
|
2027
|
-
* The plain form a reader would expect: the selector stripped away, or the compatibility
|
|
2028
|
-
* ideograph's canonical unified form. Undefined when that plain form is identical to the original,
|
|
2029
|
-
* since NFKC does not touch selectors and a suggestion equal to the input helps no one.
|
|
2030
|
-
*/
|
|
2031
|
-
function suggestPlainForm(segment) {
|
|
2032
|
-
const withoutSelectors = Array.from(segment).filter((character) => {
|
|
2033
|
-
const codePoint = character.codePointAt(0);
|
|
2034
|
-
return codePoint !== void 0 && !isSelector(codePoint);
|
|
2035
|
-
}).join("");
|
|
2036
|
-
if (withoutSelectors === "") return void 0;
|
|
2037
|
-
const normalized = Array.from(withoutSelectors).map((character) => {
|
|
2038
|
-
const codePoint = character.codePointAt(0);
|
|
2039
|
-
return codePoint !== void 0 && isCompatibilityIdeograph(codePoint) ? character.normalize("NFKC") : character;
|
|
2040
|
-
}).join("");
|
|
2041
|
-
return normalized === segment ? void 0 : normalized;
|
|
2071
|
+
return codePoint >= 6155 && codePoint <= 6157 || codePoint === 6159 || codePoint >= 65024 && codePoint <= 65039 || codePoint >= 63744 && codePoint <= 64255 || codePoint >= 194560 && codePoint <= 195103 || codePoint >= 917760 && codePoint <= 917999;
|
|
2042
2072
|
}
|
|
2043
2073
|
/**
|
|
2044
2074
|
* Scans the source rather than the display text: notation removal must not hide a variant.
|
|
2045
2075
|
*/
|
|
2046
|
-
const variantCharacterRule = () => ({
|
|
2047
|
-
|
|
2048
|
-
|
|
2049
|
-
|
|
2050
|
-
|
|
2051
|
-
},
|
|
2052
|
-
check: (manuscript, context) => {
|
|
2053
|
-
for (const { index, segment } of graphemeSegmenter.segment(manuscript.source)) {
|
|
2054
|
-
if (!Array.from(segment).some(isVariantCharacter)) continue;
|
|
2055
|
-
const range = sourceRange(manuscript, {
|
|
2056
|
-
start: index,
|
|
2057
|
-
end: index + segment.length
|
|
2058
|
-
});
|
|
2059
|
-
const suggestion = suggestPlainForm(segment);
|
|
2060
|
-
if (suggestion === void 0) context.report({
|
|
2061
|
-
range,
|
|
2062
|
-
messageId: "default"
|
|
2063
|
-
});
|
|
2064
|
-
else context.report({
|
|
2065
|
-
range,
|
|
2066
|
-
messageId: "with-suggestion",
|
|
2067
|
-
data: { suggestion }
|
|
2068
|
-
});
|
|
2069
|
-
}
|
|
2070
|
-
}
|
|
2076
|
+
const variantCharacterRule = () => defineParsedRule("kg/variant-character", "異体字または字形選択子が使われています", (manuscript, report) => {
|
|
2077
|
+
for (const { index, segment } of graphemeSegmenter.segment(manuscript.source)) if (Array.from(segment).some(isVariantCharacter)) report(sourceRange(manuscript, {
|
|
2078
|
+
start: index,
|
|
2079
|
+
end: index + segment.length
|
|
2080
|
+
}));
|
|
2071
2081
|
});
|
|
2072
2082
|
//#endregion
|
|
2073
2083
|
//#region src/proofreading/rules/default-rules.ts
|