hanlint 0.0.18 → 0.0.19

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.
Files changed (125) hide show
  1. package/README.md +17 -16
  2. package/data/calqueFrame.json +20 -20
  3. package/data/cliches.json +33 -33
  4. package/data/confusable.json +20 -20
  5. package/data/easyWords.json +12 -12
  6. package/data/exemplars.json +30 -30
  7. package/data/hiddenDeps.txt +11 -11
  8. package/data/japaneseLoan.json +27 -27
  9. package/data/koreanProse.txt +31 -31
  10. package/data/patterns.json +26 -26
  11. package/data/phraseRepeat.json +2 -2
  12. package/data/platformApis.txt +11 -11
  13. package/data/redundantPair.json +16 -16
  14. package/data/ruleDocs.json +56 -56
  15. package/data/screenNarration.json +10 -10
  16. package/data/screenSentence.json +12 -12
  17. package/data/screenTone.json +6 -6
  18. package/data/screenWord.json +70 -70
  19. package/data/spacing.json +11 -11
  20. package/data/spelling.json +29 -29
  21. package/data/translationese.json +23 -24
  22. package/data/usageCounts.report.json +2 -2
  23. package/data/version.json +1 -1
  24. package/package.json +1 -1
  25. package/src/analysis/grammar/ending.js +1 -1
  26. package/src/analysis/grammar/hangul.js +4 -4
  27. package/src/analysis/grammar/josa.js +1 -1
  28. package/src/analysis/grammar/register.js +1 -1
  29. package/src/analysis/grammar/voice.js +1 -1
  30. package/src/analysis/index.js +2 -2
  31. package/src/analysis/josa.js +1 -1
  32. package/src/analysis/splitSentences.js +1 -1
  33. package/src/analysis/tokenize.js +13 -13
  34. package/src/baseline/store.js +8 -8
  35. package/src/cli/help.js +11 -11
  36. package/src/cli/main.js +135 -124
  37. package/src/cli/walk.js +1 -1
  38. package/src/cli/welcome.js +8 -8
  39. package/src/config/loadConfig.js +3 -3
  40. package/src/config/patch.js +6 -6
  41. package/src/config/readerContract.js +26 -26
  42. package/src/config/settings.js +30 -28
  43. package/src/config/toml.js +9 -9
  44. package/src/data/exemplars.js +21 -21
  45. package/src/data/load.js +6 -6
  46. package/src/data/operations.js +14 -14
  47. package/src/data/patches.js +10 -10
  48. package/src/data/patterns.js +5 -5
  49. package/src/data/profiles.js +5 -5
  50. package/src/data/prose.js +5 -5
  51. package/src/document/model.js +4 -4
  52. package/src/document/parseMarkdown.js +7 -7
  53. package/src/document/plainText.js +3 -3
  54. package/src/document/sourceText.js +24 -24
  55. package/src/edit/applyFixes.js +0 -0
  56. package/src/fingerprint/build.js +6 -6
  57. package/src/fingerprint/codeBlocks.js +3 -3
  58. package/src/fingerprint/codeMarkers.js +4 -4
  59. package/src/fingerprint/dictionaries.js +7 -7
  60. package/src/fingerprint/markers.js +7 -7
  61. package/src/fingerprint/readerState.js +9 -9
  62. package/src/fingerprint/sourceSentence.js +1 -1
  63. package/src/fingerprint/topics.js +1 -1
  64. package/src/guard/contract.js +6 -6
  65. package/src/guard/editLocality.js +1 -1
  66. package/src/guard/outline.js +1 -1
  67. package/src/guard/receipt.js +13 -13
  68. package/src/guard/surface.js +4 -4
  69. package/src/index.js +6 -6
  70. package/src/learn/edits.js +2 -2
  71. package/src/learn/operations.js +1 -1
  72. package/src/regex.js +2 -2
  73. package/src/report/compactReport.js +3 -3
  74. package/src/report/fingerprintJson.js +2 -2
  75. package/src/report/githubReport.js +1 -1
  76. package/src/report/jsonReport.js +2 -2
  77. package/src/report/registerMatch.js +2 -2
  78. package/src/report/sheet.js +16 -16
  79. package/src/report/textReport.js +12 -12
  80. package/src/report/writingSpec.js +15 -15
  81. package/src/rules/code/inputFileSource.js +1 -1
  82. package/src/rules/code/installImport.js +2 -2
  83. package/src/rules/code/platformApi.js +1 -1
  84. package/src/rules/document/countMismatch.js +2 -1
  85. package/src/rules/document/enoughOnce.js +2 -1
  86. package/src/rules/document/fieldEcho.js +10 -6
  87. package/src/rules/document/keywordHeading.js +1 -1
  88. package/src/rules/document/keywordMissing.js +1 -1
  89. package/src/rules/document/noQuestion.js +2 -2
  90. package/src/rules/document/promiseRecall.js +1 -1
  91. package/src/rules/document/tableOddCell.js +1 -1
  92. package/src/rules/finding.js +4 -4
  93. package/src/rules/paragraph/factListParagraph.js +1 -1
  94. package/src/rules/paragraph/paraFragment.js +1 -1
  95. package/src/rules/readerDebt.js +1 -1
  96. package/src/rules/registry.js +26 -26
  97. package/src/rules/sentence/connectorRepeat.js +2 -1
  98. package/src/rules/sentence/danglingDeixis.js +1 -1
  99. package/src/rules/sentence/dash.js +1 -1
  100. package/src/rules/sentence/deixis.js +1 -1
  101. package/src/rules/sentence/doubleNegative.js +2 -1
  102. package/src/rules/sentence/doublePassive.js +2 -1
  103. package/src/rules/sentence/draftHistory.js +2 -1
  104. package/src/rules/sentence/endingRepeat.js +3 -2
  105. package/src/rules/sentence/euiChain.js +1 -1
  106. package/src/rules/sentence/fillerOpener.js +1 -1
  107. package/src/rules/sentence/imperativePeriod.js +1 -1
  108. package/src/rules/sentence/longSentence.js +1 -1
  109. package/src/rules/sentence/negationRedefine.js +1 -1
  110. package/src/rules/sentence/numberOrphan.js +1 -1
  111. package/src/rules/sentence/outsideProfile.js +8 -3
  112. package/src/rules/sentence/phraseRepeat.js +2 -1
  113. package/src/rules/shared/candidates.js +2 -2
  114. package/src/rules/shared/dictionaryRule.js +9 -2
  115. package/src/rules/shared/repeat.js +2 -2
  116. package/src/rules/structure/bridgeRepeat.js +3 -3
  117. package/src/rules/structure/emojiBullet.js +1 -1
  118. package/src/rules/structure/headingSkip.js +1 -1
  119. package/src/rules/structure/headingUniform.js +2 -1
  120. package/src/rules/structure/loneSubheading.js +1 -1
  121. package/src/rules/structure/moreLater.js +1 -1
  122. package/src/text.js +2 -2
  123. package/src/usage/counts.js +3 -3
  124. package/src/usage/index.js +3 -3
  125. package/src/usage/sentences.js +17 -17
@@ -3,112 +3,111 @@
3
3
  {
4
4
  "pattern": "에 있어서",
5
5
  "fix": "에서",
6
- "why": "번역투다. `분야에 있어서` 는 `분야에서` 다",
6
+ "why": "번역투다. `분야에 있어서`는 `분야에서`로 쓴다",
7
7
  "source": "urimal.org 김민지 2020"
8
8
  },
9
9
  {
10
10
  "pattern": "에 위치해 있",
11
11
  "fix": "에 있",
12
- "why": "번역투다. `서울에 위치해 있다` 는 `서울에 있다` 다",
12
+ "why": "번역투다. `서울에 위치해 있다`는 `서울에 있다`로 쓴다",
13
13
  "source": "egowriting 번역투 고치기"
14
14
  },
15
15
  {
16
16
  "pattern": "에 위치하",
17
17
  "fix": "에 있",
18
- "why": "번역투다. `평창에 위치하고 있습니다` 는 `평창에 있습니다` 다",
18
+ "why": "번역투다. `평창에 위치하고 있습니다`는 `평창에 있습니다`로 쓴다",
19
19
  "source": "egowriting 번역투 고치기"
20
20
  },
21
21
  {
22
- "pattern": "([가-힣])로부터",
23
- "fix": "$1에게",
24
- "why": "번역투다. `상사로부터 인정받다` 는 `상사에게 인정받다` 다. 출발점이 장소면 `에서` 다",
22
+ "pattern": "(?<=[가-힣])로부터",
23
+ "why": "번역투다. 받는 사람이면 `에게`, 출발점이면 `에서`로 쓴다. `상사로부터 인정받다`는 `상사에게 인정받다`로, `부산으로부터 왔다`는 `부산에서 왔다`로 쓴다",
25
24
  "source": "egowriting 번역투 고치기"
26
25
  },
27
26
  {
28
27
  "pattern": "음에도 불구하고",
29
28
  "fix": "지만",
30
- "why": "번역투다. `늦었음에도 불구하고` 는 `늦었지만` 이다",
29
+ "why": "번역투다. `늦었음에도 불구하고`는 `늦었지만`으로 쓴다",
31
30
  "source": "egowriting 번역투 고치기"
32
31
  },
33
32
  {
34
33
  "pattern": "에도 불구하고",
35
- "why": "번역투다. `지만` 이나 `는데도` 로 푼다",
34
+ "why": "번역투다. `-지만`이나 `-는데도`로 풀어 쓴다",
36
35
  "source": "egowriting 번역투 고치기"
37
36
  },
38
37
  {
39
38
  "pattern": "([가-힣])(?:을|를) 통해(?:서)?",
40
- "why": "through 의 직역이다. `밤샘 공부를 통해` 는 `밤샘 공부로` 다. 수단이면 `로`, 경로면 `거쳐` 다",
39
+ "why": "영어 through를 그대로 옮긴 말이다. `밤샘 공부를 통해` 대신 `밤샘 공부로`라고 쓴다. 수단이면 `로`, 경로면 `거쳐`를 쓴다",
41
40
  "source": "한빛미디어 번역투 TOP 12, im-not-ai A-2"
42
41
  },
43
42
  {
44
43
  "pattern": "에 의(?:해|하여|해서)",
45
- "why": "by 피동의 직역이다. 한 것을 주어로 세워 능동으로 쓴다. `보육원에 의해 보호되었다` 는 `보육원이 보호했다` 다",
44
+ "why": "영어 수동태의 by를 그대로 옮긴 말이다. 행동한 쪽을 주어로 세워 능동으로 쓴다. `보육원에 의해 보호되었다`는 `보육원이 보호했다`로 쓴다",
46
45
  "source": "urimal.org 김민지 2020"
47
46
  },
48
47
  {
49
48
  "pattern": "(?:으)?로 인(?:해|하여)",
50
- "why": "번역투다. `국민투표로 인하여 결정` 은 `국민투표로 결정` 이다. 원인이면 `때문에` 다",
49
+ "why": "번역투다. `국민투표로 인하여 결정`은 `국민투표로 결정`으로 쓴다. 원인이면 `때문에`를 쓴다",
51
50
  "source": "urimal.org 김민지 2020"
52
51
  },
53
52
  {
54
53
  "pattern": "에 대(?:해|하여|해서)",
55
- "why": "번역투다. `물음에 대해 답하다` 는 `물음에 답하다` 다. 목적어로 되돌린다",
54
+ "why": "번역투다. `물음에 대해 답하다`는 `물음에 답하다`로 쓴다. 목적어로 되돌린다",
56
55
  "source": "urimal.org 김민지 2020"
57
56
  },
58
57
  {
59
58
  "pattern": "에 관(?:해|하여|해서)",
60
- "why": "번역투다. 목적어나 `~은` 으로 되돌린다",
59
+ "why": "번역투다. 목적어나 `~은`으로 되돌린다",
61
60
  "source": "urimal.org 김민지 2020"
62
61
  },
63
62
  {
64
63
  "pattern": "에 대한",
65
- "why": "번역투다. `문제점에 대한 이해` 는 `문제점을 아는 것` 이다. 목적어로 되돌린다",
64
+ "why": "번역투다. `문제점에 대한 이해`는 `문제점을 아는 것`으로 쓴다. 목적어로 되돌린다",
66
65
  "source": "urimal.org 김민지 2020"
67
66
  },
68
67
  {
69
68
  "pattern": "에 관한",
70
- "why": "번역투다. `설정에 관한 설명` 은 `설정 설명` 이다. 목적어나 `~은` 으로 되돌린다",
69
+ "why": "번역투다. `설정에 관한 설명`은 `설정 설명`으로 쓴다. 목적어나 `~은`으로 되돌린다",
71
70
  "source": "urimal.org 김민지 2020"
72
71
  },
73
72
  {
74
73
  "pattern": "(?:을|를) 위(?:해|하여|해서)",
75
- "why": "번역투다. `여행을 가기 위해` 는 `여행 가려고` 다",
74
+ "why": "번역투다. `여행을 가기 위해`는 `여행 가려고`로 쓴다",
76
75
  "source": "urimal.org 김민지 2020"
77
76
  },
78
77
  {
79
78
  "pattern": "(?:을|를) 필요로 하",
80
- "why": "번역투다. `사회가 필요로 하는 것` 은 `사회가 필요한 것` 이다",
79
+ "why": "번역투다. `사회가 필요로 하는 것`은 `사회가 필요한 것`으로 쓴다",
81
80
  "source": "서울신문 바른 말글 2018"
82
81
  },
83
82
  {
84
83
  "pattern": "하는 중(?:이다|입니다|이었다)",
85
- "why": "진행형 직역이다. `도착하는 중입니다` 는 `도착합니다` 다",
84
+ "why": "영어 진행형을 그대로 옮긴 말이다. `도착하는 중입니다`는 `도착합니다`로 쓴다",
86
85
  "source": "egowriting 번역투 고치기"
87
86
  },
88
87
  {
89
88
  "pattern": "[가-힣](?:으로의|에의) ",
90
- "why": "전치사구를 명사화한 번역투다. `성공으로의 길` 은 `성공에 이르는 길` 이다",
89
+ "why": "전치사구를 명사로 만든 번역투다. `성공으로의 길`은 `성공에 이르는 길`로 쓴다",
91
90
  "source": "국립국어원 새국어생활 1990"
92
91
  },
93
92
  {
94
93
  "pattern": "에 다름 아니",
95
- "why": "일본어 직역이다. `바로 ~이다` 로 쓴다",
94
+ "why": "일본어를 그대로 옮긴 말이다. `바로 ~이다`로 쓴다",
96
95
  "source": "부산일보 바른말 광 2018"
97
96
  },
98
97
  {
99
98
  "pattern": "(?:이|가) 가능(?:하다|합니다|해진다)",
100
- "why": "possible 의 직역이다. `스킵이 가능하다` 는 `스킵할 수 있다` 다",
99
+ "why": "영어 possible을 그대로 옮긴 말이다. `스킵이 가능하다`는 `스킵할 수 있다`로 쓴다",
101
100
  "source": "나무위키 번역체 문장"
102
101
  },
103
102
  {
104
103
  "pattern": "하게 만들(?:었|었습|었다|다)",
105
- "why": "make 사동의 직역이다. `나를 행복하게 만들었다` 는 `난 그 일로 행복했다` 다",
104
+ "why": "영어 make를 쓴 사동문을 그대로 옮긴 말이다. `나를 행복하게 만들었다`는 `난 그 일로 행복했다`로 쓴다",
106
105
  "source": "hanmal 번역투 정리 2011"
107
106
  },
108
107
  {
109
108
  "pattern": "(?:을|를) 가지고 있",
110
- "why": "have 의 직역이다. `목소리를 가지고 있다` 는 `목소리가 좋다`, `책을 가지고 있다` 는 `책이 있다` 다",
111
- "source": "김순영 2012 (이근희 2005 재인용)"
109
+ "why": "영어 have를 그대로 옮긴 말이다. `목소리를 가지고 있다`는 `목소리가 좋다`로, `책을 가지고 있다`는 `책이 있다`로 쓴다",
110
+ "source": "김순영 2012, 이근희 2005 재인용"
112
111
  }
113
112
  ]
114
113
  }
@@ -1644,7 +1644,7 @@
1644
1644
  "(?<![가-힣])잔고": 165
1645
1645
  },
1646
1646
  "redundantPair": {
1647
- "(?:또)?다시 재(?!(?:력|주|산|물|화|벌|원|무|정|능|치|간|료|목|질|난|해|앙|고|판){조사})([가-힣]+)": 12,
1647
+ "(?:또)?다시 재(?!(?:력|주|산|물|화|벌|원|무|정|능|치|간|료|목|질|난|해|앙|고|판){조사})(?!(?:거나|면|서|며|니|는|어|어서|었다|야|요|다|도|지)(?![가-힣]))([가-힣]+)": 12,
1648
1648
  "결실을 맺": 25,
1649
1649
  "과반수 이상": 94,
1650
1650
  "남은 여생": 1,
@@ -1656,8 +1656,8 @@
1656
1656
  "(?:을|를) 위(?:해|하여|해서)": 3184,
1657
1657
  "(?:을|를) 필요로 하": 1196,
1658
1658
  "(?:이|가) 가능(?:하다|합니다|해진다)": 2099,
1659
+ "(?<=[가-힣])로부터": 3184,
1659
1660
  "([가-힣])(?:을|를) 통해(?:서)?": 3171,
1660
- "([가-힣])로부터": 3184,
1661
1661
  "[가-힣](?:으로의|에의) ": 1906,
1662
1662
  "에 관(?:해|하여|해서)": 1676,
1663
1663
  "에 관한": 3189,
package/data/version.json CHANGED
@@ -1 +1 @@
1
- {"version": "0.0.18"}
1
+ {"version": "0.0.19"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hanlint",
3
- "version": "0.0.18",
3
+ "version": "0.0.19",
4
4
  "description": "한국어 글 검사기. Korean prose linter for Markdown: translationese, noun piles, double passives, weak structure. Zero dependencies",
5
5
  "type": "module",
6
6
  "bin": {
@@ -1,5 +1,5 @@
1
1
  // @ts-check
2
- /** 파이썬 `analysis/grammar/ending.py`와 `predicate.py`의 투영. 종결 서술어를 풀고 세 문체로 짠다. */
2
+ /** 파이썬 `analysis/grammar/ending.py`와 `predicate.py`를 옮긴 것. 종결 서술어를 풀고 세 문체로 짠다. */
3
3
  import { loadLines } from "../../data/load.js";
4
4
  import * as hangul from "./hangul.js";
5
5
 
@@ -1,6 +1,6 @@
1
1
  // @ts-check
2
2
  /**
3
- * 한글 음절의 자모 산술. 파이썬 `analysis/grammar/hangul.py` 의 투영이다. 표 대신 산술로 하는 까닭은 그 파일이 소유한다.
3
+ * 한글 음절의 자모 산술. 파이썬 `analysis/grammar/hangul.py`를 옮긴 것이다. 표 대신 산술로 하는 까닭은 그 파일에 적었다.
4
4
  */
5
5
 
6
6
  export const BASE = 0xac00;
@@ -31,7 +31,7 @@ export const EU = 18;
31
31
  export const UI = 19;
32
32
  export const IH = 20;
33
33
 
34
- // 종성 자리 번호 가운데 형태 층이 이름으로 부르는 것.
34
+ // 종성 자리 번호 가운데 형태 부분이 이름으로 부르는 것.
35
35
  export const NONE = 0;
36
36
  export const GIYEOK = 1;
37
37
  export const NIEUN = 4;
@@ -44,7 +44,7 @@ export const SSANGSIOT = 20;
44
44
  export const IEUNG = 21;
45
45
  export const HIEUT = 27;
46
46
 
47
- /** 양성 모음. 이 모음 뒤에는 `아` 가 붙고 나머지에는 `어` 가 붙는다. */
47
+ /** 양성 모음. 이 모음 뒤에는 `아`가 붙고 나머지에는 `어`가 붙는다. */
48
48
  export const BRIGHT = new Set([A, OH, YA, YO, WA]);
49
49
 
50
50
  /** @param {string} ch */
@@ -88,7 +88,7 @@ export function withVowel(ch, vowel) {
88
88
  return join(initial, vowel, final);
89
89
  }
90
90
 
91
- /** 낱말의 마지막 한글 음절. 없으면 null. @param {string} word */
91
+ /** 단어의 마지막 한글 음절. 없으면 null. @param {string} word */
92
92
  export function lastSyllable(word) {
93
93
  return word && isSyllable(word[word.length - 1]) ? word[word.length - 1] : null;
94
94
  }
@@ -1,5 +1,5 @@
1
1
  // @ts-check
2
- /** 파이썬 `analysis/grammar/josa.py`의 투영. 받침에 따라 조사 꼴을 맞춘다. */
2
+ /** 파이썬 `analysis/grammar/josa.py`를 옮긴 것. 받침에 따라 조사 꼴을 맞춘다. */
3
3
  import * as hangul from "./hangul.js";
4
4
 
5
5
  const PAIRS = [
@@ -1,5 +1,5 @@
1
1
  // @ts-check
2
- /** 파이썬 `analysis/grammar/register.py`의 투영. 글의 문체를 판별하고 문장 끝 서술어를 맞춘다. */
2
+ /** 파이썬 `analysis/grammar/register.py`를 옮긴 것. 글의 문체를 판별하고 문장 끝 서술어를 맞춘다. */
3
3
  import * as hangul from "./hangul.js";
4
4
  import { HAEYO, HANDA, HAPNIDA, REGISTERS, parsePredicate, render } from "./ending.js";
5
5
 
@@ -1,5 +1,5 @@
1
1
  // @ts-check
2
- /** 파이썬 `analysis/grammar/voice.py`의 투영. 확정되는 사동과 피동의 겹만 분해한다. */
2
+ /** 파이썬 `analysis/grammar/voice.py`를 옮긴 것. 확정되는 사동과 피동의 겹만 분해한다. */
3
3
  import { loadLines } from "../../data/load.js";
4
4
 
5
5
  export const PASSIVE = "피동";
@@ -1,7 +1,7 @@
1
1
  // @ts-check
2
2
  /**
3
- * 분석 층. 문장 분리와 어절 판정을 표층 (어절과 꼬리 사전) 으로 한다. 형태소 분석기는 없다. 파이썬 analysis/__init__.py 와 같다.
4
- * 지문이 필요로 하는 것은 넷이다. splitSentences, euiCount 와 euiAdjacent, longestNounRun, doublePassives.
3
+ * 분석 부분. 문장 분리와 어절 판정을 글자 모양(어절과 꼬리 사전)만으로 한다. 형태소 분석기는 없다. 파이썬 analysis/__init__.py와 같다.
4
+ * 글 분석에 필요한 것은 넷이다. splitSentences, euiCount와 euiAdjacent, longestNounRun, doublePassives.
5
5
  */
6
6
  export { splitSentences } from "./splitSentences.js";
7
7
  export { doublePassives, doublePassiveSpans, euiAdjacent, euiCount, longestNounRun, nounRuns } from "./tokenize.js";
@@ -1,2 +1,2 @@
1
- // 이전 deep path 호환. 정본 투영은 grammar/josa.js다.
1
+ // 이전 deep path 호환. 실제 구현은 grammar/josa.js에 있다.
2
2
  export * from "./grammar/josa.js";
@@ -1,5 +1,5 @@
1
1
  // @ts-check
2
- /** 종결 부호 뒤 공백에서 문장을 나눈다. 파이썬 analysis/splitSentences.py 와 같다. */
2
+ /** 종결 부호 뒤 공백에서 문장을 나눈다. 파이썬 analysis/splitSentences.py와 같다. */
3
3
 
4
4
  const TERMINAL = /[.?!]+\s+(?=\S)/g;
5
5
  const ABBREVIATION = /(?:^|[^A-Za-z])[A-Za-z]$/;
@@ -1,6 +1,6 @@
1
1
  // @ts-check
2
2
  /**
3
- * 어절 단위 근사. 조사와 어미 꼬리 사전으로 명사 어절을 가려낸다. 파이썬 analysis/tokenize.py 와 같은 판정이다.
3
+ * 어절 단위 어림. 조사와 어미 꼬리 사전으로 명사 어절을 가려낸다. 파이썬 analysis/tokenize.py와 같은 판정이다.
4
4
  * 띄어 쓴 조사와 계사는 앞 어절에 붙은 것으로, 영문 어절 연속은 한 덩어리로, 수사와 바로 뒤 단위는 수량으로 본다.
5
5
  */
6
6
  import { loadLines } from "../data/load.js";
@@ -9,7 +9,7 @@ import { splitWords, stripChars } from "../text.js";
9
9
  const HANGUL = /[가-힣]/;
10
10
  const HANGUL_WORD = /^[가-힣]+$/;
11
11
  const WORD_CHARS = /^[가-힣A-Za-z0-9]+$/;
12
- /** 관형격 조사 의. 영문과 숫자 뒤 (API의, 600MiB의) 와 닫는 괄호 뒤 (L7(HTTP)의) 도 센다. 파이썬 tokenize.py 와 같다. */
12
+ /** 관형격 조사 `의`. 영문과 숫자 뒤(`API의`, `600MiB의`)와 닫는 괄호 뒤(`L7(HTTP)의`)도 센다. 파이썬 tokenize.py와 같다. */
13
13
  const GENITIVE = /([가-힣A-Za-z0-9]+|\))의(?=[\s,.)\]]|$)/g;
14
14
  const SPACED_GENITIVE = /(?<=\s)의(?=\s)/g;
15
15
  export const COPULA = /^(?:이(?:고|며|다|라|란|면|라서|므로|지만|어서|었다|었고)|인|인데|입니다|였다)$/;
@@ -77,8 +77,8 @@ export function inNouns() {
77
77
  }
78
78
 
79
79
  /**
80
- * `도구인`, `규모인` 처럼 명사에 계사 관형형 인 이 붙은 어절. 서술어라 명사 나열을 끊는다. 어간이 두 음절 이상일 때만이고
81
- * 외국인 같은 세 음절 명사는 data/inNouns.txt 가 뺀다. 파이썬 tokenize.py 와 같다.
80
+ * `도구인`, `규모인`처럼 명사에 계사 관형형 `인`이 붙은 어절. 서술어라 명사 나열을 끊는다. 어간이 두 음절 이상일 때만이고
81
+ * 외국인 같은 세 음절 명사는 data/inNouns.txt가 뺀다. 파이썬 tokenize.py와 같다.
82
82
  * @param {string} core
83
83
  */
84
84
  export function isCopulaAdnominal(core) {
@@ -126,12 +126,12 @@ export function isBareNoun(core) {
126
126
  }
127
127
 
128
128
  /**
129
- * 명사 어절 연속의 최대 길이. 의존명사와 관형사와 부사 (`수`, `몇`, `직접`) 는 조사도 어미도 안 붙어
130
- * 표층으로는 명사로 보이지만 명사 쌓기의 재료가 아니다. data/nonNouns.txt 가 그 목록이고 연속을 끊는다.
129
+ * 명사 어절 연속의 최대 길이. 의존명사와 관형사와 부사(`수`, `몇`, `직접`)는 조사도 어미도 안 붙어
130
+ * 글자 모양으로는 명사로 보이지만 명사 쌓기의 재료가 아니다. data/nonNouns.txt가 그 목록이고 연속을 끊는다.
131
131
  * @param {string} text
132
132
  */
133
133
  /**
134
- * 명사 어절 연속마다 [어절들, 센 길이]. 뜻은 파이썬 analysis/tokenize.py 의 nounRuns 가 소유한다.
134
+ * 명사 어절 연속마다 [어절들, 센 길이]. 뜻은 파이썬 analysis/tokenize.py의 nounRuns가 정한다.
135
135
  * 수사와 단위는 세지도 끊지도 않고 어절에도 안 넣는다. 잇달은 영문 어절은 하나로 센다.
136
136
  * @param {string} text @returns {[string[], number][]}
137
137
  */
@@ -167,14 +167,14 @@ export function nounRuns(text) {
167
167
  return runs;
168
168
  }
169
169
 
170
- /** 명사 어절 연속의 최대 길이. nounRuns 의 센 길이 가운데 가장 큰 것. @param {string} text */
170
+ /** 명사 어절 연속의 최대 길이. nounRuns의 센 길이 가운데 가장 큰 것. @param {string} text */
171
171
  export function longestNounRun(text) {
172
172
  let longest = 0;
173
173
  for (const [, length] of nounRuns(text)) longest = Math.max(longest, length);
174
174
  return longest;
175
175
  }
176
176
 
177
- /** 관형격 조사 의 가 붙은 자리 [시작, 끝]. 정의, 회의 처럼 의 로 끝나는 낱말은 빼고 띄어 쓴 의 는 넣는다. @param {string} text @returns {[number, number][]} */
177
+ /** 관형격 조사 `의`가 붙은 곳 [시작, 끝]. `정의`, `회의`처럼 `의`로 끝나는 단어는 빼고 띄어 쓴 `의`는 넣는다. @param {string} text @returns {[number, number][]} */
178
178
  export function genitiveSpans(text) {
179
179
  /** @type {[number, number][]} */
180
180
  const spans = [];
@@ -185,13 +185,13 @@ export function genitiveSpans(text) {
185
185
  return spans.sort((a, b) => a[0] - b[0] || a[1] - b[1]);
186
186
  }
187
187
 
188
- /** 의 로 끝나는 어절 둘이 붙어 있는가 (회사의 팀의). 사이가 공백뿐이어야 한다. @param {string} text */
188
+ /** `의`로 끝나는 어절 둘이 붙어 있는가(`회사의 팀의`). 사이가 공백뿐이어야 한다. @param {string} text */
189
189
  export function euiAdjacent(text) {
190
190
  const spans = genitiveSpans(text);
191
191
  return spans.some((span, index) => index > 0 && /^\s+$/.test(text.slice(spans[index - 1][1], span[0])));
192
192
  }
193
193
 
194
- /** 관형격 조사 의 의 수. @param {string} text */
194
+ /** 관형격 조사 `의`의 수. @param {string} text */
195
195
  export function euiCount(text) {
196
196
  return genitiveSpans(text).length;
197
197
  }
@@ -220,7 +220,7 @@ export function doublePassivePattern() {
220
220
  return passivePattern;
221
221
  }
222
222
 
223
- /** 이중 피동의 [시작, 끝, `되어지` 꼴 표층형]. @param {string} text @returns {[number, number, string][]} */
223
+ /** 이중 피동의 [시작, 끝, `되어지` 꼴로 쓴 글자]. @param {string} text @returns {[number, number, string][]} */
224
224
  export function doublePassiveSpans(text) {
225
225
  return [...text.matchAll(doublePassivePattern())].map((match) => {
226
226
  const start = /** @type {number} */ (match.index);
@@ -228,7 +228,7 @@ export function doublePassiveSpans(text) {
228
228
  });
229
229
  }
230
230
 
231
- /** 이중 피동의 표층형을 `되어지` 꼴로. @param {string} text */
231
+ /** 이중 피동의 글자를 `되어지` 꼴로. @param {string} text */
232
232
  export function doublePassives(text) {
233
233
  return doublePassiveSpans(text).map(([, , surface]) => surface);
234
234
  }
@@ -1,7 +1,7 @@
1
1
  // @ts-check
2
2
  /**
3
- * 이미 있는 지적을 잠가 두고 새로 생긴 것만 막는다. 파이썬 `hanlint/baseline/store.py` 의 투영이다.
4
- * 왜 줄 번호가 아니라 글자인지, 왜 사람이 읽는 꼴인지는 그 파일의 docstring 이 정본이다.
3
+ * 이미 있는 지적을 baseline 파일에 적어 두고 새로 생긴 지적만 막는다. 파이썬 `hanlint/baseline/store.py`를 옮긴 것이다.
4
+ * 왜 줄 번호가 아니라 글자인지, 왜 사람이 읽는 형식인지는 그 파일의 docstring에 있다.
5
5
  */
6
6
  import { existsSync, readFileSync } from "node:fs";
7
7
  import { dirname, posix, relative, resolve, sep } from "node:path";
@@ -20,7 +20,7 @@ export function keyOf(finding) {
20
20
  return `${finding.rule}\u0000${normalizeQuote(finding.quote)}`;
21
21
  }
22
22
 
23
- /** 잠금 파일이 있는 폴더 기준의 상대 경로. @param {string} path @param {string | null} target */
23
+ /** baseline 파일이 있는 폴더에서 본 상대 경로. @param {string} path @param {string | null} target */
24
24
  export function pathKey(path, target) {
25
25
  if (!target || !path || path.startsWith("<")) return path;
26
26
  return relative(dirname(resolve(target)), resolve(path)).split(sep).join(posix.sep);
@@ -44,7 +44,7 @@ export class Baseline {
44
44
  return (this.locked.get(pathKey(path, this.source)) ?? new Set()).has(keyOf(finding));
45
45
  }
46
46
 
47
- /** 잠기지 않은 지적만 남긴다. @param {string} path @param {import("../rules/finding.js").Finding[]} findings */
47
+ /** baseline 파일에 없는 지적만 남긴다. @param {string} path @param {import("../rules/finding.js").Finding[]} findings */
48
48
  keep(path, findings) {
49
49
  return findings.filter((finding) => !this.isLocked(path, finding));
50
50
  }
@@ -60,7 +60,7 @@ export function build(results, target = null) {
60
60
  return new Baseline(locked, target ? resolve(target) : null);
61
61
  }
62
62
 
63
- /** 사람이 읽고 리뷰하는 꼴. 정렬해 담아 같은 입력에 같은 파일이 나온다. @param {Baseline} baseline */
63
+ /** 사람이 읽고 리뷰하는 형식. 정렬해 담아 같은 입력에 같은 파일이 나온다. @param {Baseline} baseline */
64
64
  export function render(baseline) {
65
65
  /** @type {Record<string, {rule: string, quote: string}[]>} */
66
66
  const files = {};
@@ -80,7 +80,7 @@ export function render(baseline) {
80
80
  /** @param {string} text @param {string | null} [source] */
81
81
  export function parse(text, source = null) {
82
82
  const data = JSON.parse(text);
83
- if (data.version !== VERSION) throw new Error(`모르는 baseline 판 ${data.version}. hanlint baseline 으로 다시 만든다`);
83
+ if (data.version !== VERSION) throw new Error(`지원하지 않는 baseline 버전: ${data.version}. hanlint baseline으로 다시 만든다`);
84
84
  /** @type {Map<string, Set<string>>} */
85
85
  const locked = new Map();
86
86
  for (const [path, entries] of Object.entries(data.files ?? {})) {
@@ -92,7 +92,7 @@ export function parse(text, source = null) {
92
92
  /** @param {string} path */
93
93
  export function load(path) {
94
94
  if (!existsSync(path)) {
95
- const error = /** @type {NodeJS.ErrnoException} */ (new Error(`${path} 를 찾지 못했다`));
95
+ const error = /** @type {NodeJS.ErrnoException} */ (new Error(`파일을 찾지 못했다: ${path}`));
96
96
  error.code = "ENOENT";
97
97
  error.path = path;
98
98
  throw error;
@@ -100,7 +100,7 @@ export function load(path) {
100
100
  return parse(readFileSync(path, "utf-8"), resolve(path));
101
101
  }
102
102
 
103
- /** 지금 글에 더 없는 잠금을 지운다. @param {Baseline} baseline @param {Map<string, import("../rules/finding.js").Finding[]>} results */
103
+ /** 지금 글에 더는 없는 지적을 baseline 파일에서 지운다. @param {Baseline} baseline @param {Map<string, import("../rules/finding.js").Finding[]>} results */
104
104
  export function prune(baseline, results) {
105
105
  const kept = new Map(baseline.locked);
106
106
  for (const [path, findings] of results) {
package/src/cli/help.js CHANGED
@@ -2,14 +2,14 @@
2
2
  /** 첫 화면 다음에 보이는 작업 흐름 중심 도움말. */
3
3
 
4
4
  export function rootHelp() {
5
- return `hanlint: 한국어 마크다운에서 반복되는 결함과 계약 위반을 결정적으로 찾는다
5
+ return `hanlint: 한국어 글에서 규칙으로 잡을 수 있는 잘못을 찾는다
6
6
 
7
7
  일상 검사
8
8
  hanlint 글.md 글을 검사한다
9
9
  hanlint fix 글.md 안전한 고침만 적용한다
10
- hanlint hook AI에게 쓰기 전 작법서를 싣고, 저장한 마크다운을 같은 턴에 검사한다
10
+ hanlint hook AI가 쓰기 전에 글쓰기 지침을 넣어 주고, 저장한 마크다운을 같은 턴에 검사한다
11
11
 
12
- 요구사항 잠금
12
+ 요구사항 고정
13
13
  hanlint contract init 글.md --reader "독자" --goal "목표" --outline h2
14
14
  hanlint check contract.json 글.md --format text
15
15
  hanlint verify-patch contract.json 글.md patch.json
@@ -17,20 +17,20 @@ export function rootHelp() {
17
17
  설정과 이해
18
18
  hanlint init 설정 파일을 만든다
19
19
  hanlint doctor 적용 설정과 꺼진 규칙을 본다
20
- hanlint rules 규칙을 부류별로 본다
21
- hanlint explain <규칙> 규칙의 이유와 본보기를 본다
20
+ hanlint rules 규칙을 카테고리별로 본다
21
+ hanlint explain <규칙> 규칙의 이유와 예시를 본다
22
22
  hanlint patterns --rule <규칙> 다시 쓸 문장 틀을 본다
23
23
  hanlint spec --preset <종류> --chars 800
24
- 쓰기 전 숫자 사양을 만든다
25
- hanlint primer --preset <종류> 쓰기 전에 규칙의 고치는 법과 본보기를 한 장으로 본다
26
- hanlint sheet src/ --preset screen 소스의 화면 글을 표 하나로 내보낸다. 고친 표는 sheet apply 로 되돌려 쓴다
27
- hanlint usage "낱말 낱말" 그 종류의 실제 글에서 낱말이 쓰인 문장을 본다. 색인은 usage build 가 만든다
24
+ 쓰기 전에 볼 스펙을 만든다
25
+ hanlint primer --preset <종류> 쓰기 전에 규칙의 고치는 법과 예시를 한 장으로 본다
26
+ hanlint sheet src/ --preset screen 소스의 화면 글을 표 하나로 내보낸다. 고친 표는 sheet apply로 원본에 반영한다
27
+ hanlint usage "단어 단어" 그 종류의 실제 글에서 단어가 쓰인 문장을 본다. 색인은 usage build가 만든다
28
28
 
29
29
  추가 명령
30
30
  baseline, print
31
31
  Python 전용: audit, map, watch, profile, coverage, diff, learn, packet,
32
32
  guard, arena, blueprint, evidence, entailment, terms
33
33
 
34
- 자세한 옵션은 hanlint <명령> --help 로 본다. 폴더를 주면 그 아래 마크다운을 검사한다.
35
- 종료 코드: 0 error 없음, 1 error 있음, 2 입력이나 설정 문제. 좋은 글인지는 판정하지 않는다.`;
34
+ 명령마다의 옵션은 README의 명령 표에 있다. 폴더를 주면 그 아래 마크다운을 검사한다.
35
+ 종료 코드: 0은 오류 없음, 1은 오류 있음, 2는 입력이나 설정 문제. 좋은 글인지는 판정하지 않는다.`;
36
36
  }