chaffjs 0.29.0 → 0.30.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/dist/derived/session-hours.d.ts +26 -0
- package/dist/derived/session-hours.d.ts.map +1 -0
- package/dist/derived/session-hours.js +29 -0
- package/dist/derived/session-hours.js.map +1 -0
- package/dist/derived/time-lengths.d.ts +37 -0
- package/dist/derived/time-lengths.d.ts.map +1 -0
- package/dist/derived/time-lengths.js +168 -0
- package/dist/derived/time-lengths.js.map +1 -0
- package/dist/derived/unit-counts.d.ts +7 -0
- package/dist/derived/unit-counts.d.ts.map +1 -0
- package/dist/derived/unit-counts.js +13 -0
- package/dist/derived/unit-counts.js.map +1 -0
- package/dist/derived/working-hours.d.ts +36 -0
- package/dist/derived/working-hours.d.ts.map +1 -0
- package/dist/derived/working-hours.js +55 -0
- package/dist/derived/working-hours.js.map +1 -0
- package/dist/detectors/acronym-expansion.d.ts +1 -1
- package/dist/detectors/acronym-expansion.d.ts.map +1 -1
- package/dist/detectors/acronym-expansion.js +46 -2
- package/dist/detectors/acronym-expansion.js.map +1 -1
- package/dist/detectors/capitalised-term.d.ts +44 -0
- package/dist/detectors/capitalised-term.d.ts.map +1 -0
- package/dist/detectors/capitalised-term.js +101 -0
- package/dist/detectors/capitalised-term.js.map +1 -0
- package/dist/detectors/date-outside-period.d.ts.map +1 -1
- package/dist/detectors/date-outside-period.js +4 -1
- package/dist/detectors/date-outside-period.js.map +1 -1
- package/dist/detectors/derived-numbers.d.ts +2 -0
- package/dist/detectors/derived-numbers.d.ts.map +1 -1
- package/dist/detectors/derived-numbers.js +72 -18
- package/dist/detectors/derived-numbers.js.map +1 -1
- package/dist/detectors/name-variant.d.ts.map +1 -1
- package/dist/detectors/name-variant.js +57 -10
- package/dist/detectors/name-variant.js.map +1 -1
- package/dist/detectors/party-order.d.ts +3 -0
- package/dist/detectors/party-order.d.ts.map +1 -0
- package/dist/detectors/party-order.js +46 -0
- package/dist/detectors/party-order.js.map +1 -0
- package/dist/detectors/registry/connection-time-short.d.ts +3 -0
- package/dist/detectors/registry/connection-time-short.d.ts.map +1 -0
- package/dist/detectors/registry/connection-time-short.js +3 -0
- package/dist/detectors/registry/connection-time-short.js.map +1 -0
- package/dist/detectors/registry/duration-product-mismatch.d.ts +3 -0
- package/dist/detectors/registry/duration-product-mismatch.d.ts.map +1 -0
- package/dist/detectors/registry/duration-product-mismatch.js +3 -0
- package/dist/detectors/registry/duration-product-mismatch.js.map +1 -0
- package/dist/detectors/registry/party-order.d.ts +3 -0
- package/dist/detectors/registry/party-order.d.ts.map +1 -0
- package/dist/detectors/registry/party-order.js +3 -0
- package/dist/detectors/registry/party-order.js.map +1 -0
- package/dist/detectors/registry/requirement-lists.d.ts +3 -0
- package/dist/detectors/registry/requirement-lists.d.ts.map +1 -0
- package/dist/detectors/registry/requirement-lists.js +3 -0
- package/dist/detectors/registry/requirement-lists.js.map +1 -0
- package/dist/detectors/registry/return-type.d.ts +3 -0
- package/dist/detectors/registry/return-type.d.ts.map +1 -0
- package/dist/detectors/registry/return-type.js +3 -0
- package/dist/detectors/registry/return-type.js.map +1 -0
- package/dist/detectors/requirement-lists.d.ts +3 -0
- package/dist/detectors/requirement-lists.d.ts.map +1 -0
- package/dist/detectors/requirement-lists.js +46 -0
- package/dist/detectors/requirement-lists.js.map +1 -0
- package/dist/detectors/return-type.d.ts +3 -0
- package/dist/detectors/return-type.d.ts.map +1 -0
- package/dist/detectors/return-type.js +24 -0
- package/dist/detectors/return-type.js.map +1 -0
- package/dist/detectors/structure-tree.d.ts.map +1 -1
- package/dist/detectors/structure-tree.js +9 -1
- package/dist/detectors/structure-tree.js.map +1 -1
- package/dist/detectors/time-order.d.ts +1 -0
- package/dist/detectors/time-order.d.ts.map +1 -1
- package/dist/detectors/time-order.js +30 -4
- package/dist/detectors/time-order.js.map +1 -1
- package/dist/detectors/undefined-term.d.ts.map +1 -1
- package/dist/detectors/undefined-term.js +19 -3
- package/dist/detectors/undefined-term.js.map +1 -1
- package/dist/document.d.ts.map +1 -1
- package/dist/document.js +4 -1
- package/dist/document.js.map +1 -1
- package/dist/facts/table-facts.d.ts +2 -0
- package/dist/facts/table-facts.d.ts.map +1 -1
- package/dist/facts/table-facts.js +2 -0
- package/dist/facts/table-facts.js.map +1 -1
- package/dist/name-variants.d.ts +6 -1
- package/dist/name-variants.d.ts.map +1 -1
- package/dist/name-variants.js +68 -0
- package/dist/name-variants.js.map +1 -1
- package/dist/name-word-order.d.ts +49 -0
- package/dist/name-word-order.d.ts.map +1 -0
- package/dist/name-word-order.js +114 -0
- package/dist/name-word-order.js.map +1 -0
- package/dist/plugin.d.ts +7 -0
- package/dist/plugin.d.ts.map +1 -1
- package/dist/return-type.d.ts +33 -0
- package/dist/return-type.d.ts.map +1 -0
- package/dist/return-type.js +302 -0
- package/dist/return-type.js.map +1 -0
- package/dist/structure/connection-times.d.ts +56 -0
- package/dist/structure/connection-times.d.ts.map +1 -0
- package/dist/structure/connection-times.js +165 -0
- package/dist/structure/connection-times.js.map +1 -0
- package/dist/structure/date-order-tie.d.ts +13 -0
- package/dist/structure/date-order-tie.d.ts.map +1 -0
- package/dist/structure/date-order-tie.js +61 -0
- package/dist/structure/date-order-tie.js.map +1 -0
- package/dist/structure/date-order.d.ts +2 -2
- package/dist/structure/date-order.d.ts.map +1 -1
- package/dist/structure/date-order.js +26 -9
- package/dist/structure/date-order.js.map +1 -1
- package/dist/structure/date-outside-period.d.ts +6 -0
- package/dist/structure/date-outside-period.d.ts.map +1 -1
- package/dist/structure/date-outside-period.js +49 -3
- package/dist/structure/date-outside-period.js.map +1 -1
- package/dist/structure/date-range.d.ts +5 -2
- package/dist/structure/date-range.d.ts.map +1 -1
- package/dist/structure/date-range.js +37 -9
- package/dist/structure/date-range.js.map +1 -1
- package/dist/structure/leg-times.d.ts +2 -0
- package/dist/structure/leg-times.d.ts.map +1 -1
- package/dist/structure/leg-times.js +13 -0
- package/dist/structure/leg-times.js.map +1 -1
- package/dist/structure/party-order.d.ts +33 -0
- package/dist/structure/party-order.d.ts.map +1 -0
- package/dist/structure/party-order.js +79 -0
- package/dist/structure/party-order.js.map +1 -0
- package/dist/structure/requirement-lists.d.ts +37 -0
- package/dist/structure/requirement-lists.d.ts.map +1 -0
- package/dist/structure/requirement-lists.js +89 -0
- package/dist/structure/requirement-lists.js.map +1 -0
- package/dist/structure/time-order.d.ts +21 -0
- package/dist/structure/time-order.d.ts.map +1 -1
- package/dist/structure/time-order.js +5 -6
- package/dist/structure/time-order.js.map +1 -1
- package/dist/structure/version-order.d.ts +2 -0
- package/dist/structure/version-order.d.ts.map +1 -1
- package/dist/structure/version-order.js +10 -0
- package/dist/structure/version-order.js.map +1 -1
- package/dist/surname-near.d.ts +6 -0
- package/dist/surname-near.d.ts.map +1 -0
- package/dist/surname-near.js +31 -0
- package/dist/surname-near.js.map +1 -0
- package/dist/table-cells.d.ts +13 -0
- package/dist/table-cells.d.ts.map +1 -0
- package/dist/table-cells.js +36 -0
- package/dist/table-cells.js.map +1 -0
- package/dist/table-names.d.ts +42 -0
- package/dist/table-names.d.ts.map +1 -0
- package/dist/table-names.js +94 -0
- package/dist/table-names.js.map +1 -0
- package/package.json +3 -3
- package/rules/connection-time-short.yaml +52 -0
- package/rules/date-outside-period.yaml +4 -4
- package/rules/duration-mismatch.yaml +7 -4
- package/rules/duration-product-mismatch.yaml +73 -0
- package/rules/name-variant.yaml +8 -2
- package/rules/party-order.yaml +84 -0
- package/rules/required-listed-as-preferred.yaml +102 -0
- package/rules/return-type-mismatch.yaml +79 -0
- package/rules/undefined-term.yaml +21 -7
- package/src/derived/session-hours.ts +62 -0
- package/src/derived/time-lengths.ts +226 -0
- package/src/derived/unit-counts.ts +18 -0
- package/src/derived/working-hours.ts +97 -0
- package/src/detectors/acronym-expansion.ts +57 -2
- package/src/detectors/capitalised-term.ts +147 -0
- package/src/detectors/date-outside-period.ts +4 -1
- package/src/detectors/derived-numbers.ts +94 -21
- package/src/detectors/name-variant.ts +71 -13
- package/src/detectors/party-order.ts +50 -0
- package/src/detectors/registry/connection-time-short.ts +4 -0
- package/src/detectors/registry/duration-product-mismatch.ts +4 -0
- package/src/detectors/registry/party-order.ts +4 -0
- package/src/detectors/registry/requirement-lists.ts +4 -0
- package/src/detectors/registry/return-type.ts +4 -0
- package/src/detectors/requirement-lists.ts +65 -0
- package/src/detectors/return-type.ts +30 -0
- package/src/detectors/structure-tree.ts +12 -1
- package/src/detectors/time-order.ts +40 -5
- package/src/detectors/undefined-term.ts +20 -3
- package/src/document.ts +4 -1
- package/src/facts/table-facts.ts +3 -0
- package/src/name-variants.ts +76 -1
- package/src/name-word-order.ts +149 -0
- package/src/plugin.ts +5 -0
- package/src/return-type.ts +361 -0
- package/src/structure/connection-times.ts +226 -0
- package/src/structure/date-order-tie.ts +86 -0
- package/src/structure/date-order.ts +41 -14
- package/src/structure/date-outside-period.ts +72 -7
- package/src/structure/date-range.ts +45 -9
- package/src/structure/leg-times.ts +15 -0
- package/src/structure/party-order.ts +115 -0
- package/src/structure/requirement-lists.ts +145 -0
- package/src/structure/time-order.ts +8 -7
- package/src/structure/version-order.ts +10 -0
- package/src/surname-near.ts +31 -0
- package/src/table-cells.ts +41 -0
- package/src/table-names.ts +113 -0
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
id: party-order
|
|
2
|
+
layer: L3
|
|
3
|
+
status: stable
|
|
4
|
+
how_to_find: party-order
|
|
5
|
+
word_list: party-order-joiner
|
|
6
|
+
extra_word_lists: [party-order-label, party-short-name, party-role, company-form]
|
|
7
|
+
requires: [structure]
|
|
8
|
+
# An English contract has no fixed order for its parties: "the Supplier and the Customer" and "the Customer and the Supplier"
|
|
9
|
+
# are both plain English. Japanese contracts list 甲 before 乙 by convention, so the rule is Japanese only.
|
|
10
|
+
languages: [ja]
|
|
11
|
+
severity: info
|
|
12
|
+
|
|
13
|
+
name:
|
|
14
|
+
ja: 当事者の呼び名を、ほかの所と逆の順に並べている
|
|
15
|
+
en: Parties listed in the reverse of the document's usual order
|
|
16
|
+
|
|
17
|
+
why:
|
|
18
|
+
ja: 契約書は当事者の呼び名を決まった順に並べます(甲及び乙、甲又は乙、甲乙、甲・乙・丙)。ほかの所で「甲及び乙」と書いた契約に「乙及び甲」が一つだけあると、読み手は順を変えた意味(乙を先に立てる理由)を探して止まります。条項を貼り合わせたときや、書き直したときに起きます。並べる語(及び・又は・と・・)でつないだ呼び名だけを数え、同じ二つの呼び名を逆の順に並べた所が、ふだんの順の半分以下のときに指します。
|
|
19
|
+
en: A Japanese contract lists its parties in a fixed order (甲及び乙, 甲又は乙, 甲乙, 甲・乙・丙). In a contract that writes 甲及び乙 everywhere else, a single 乙及び甲 makes the reader look for a reason to put 乙 first. It comes from pasted or rewritten clauses. Only names joined by a listing word (及び, 又は, と, ・) are counted, and a listing is reported when its order is written at most half as often as the reverse.
|
|
20
|
+
|
|
21
|
+
message:
|
|
22
|
+
ja: 「{written}」は、この文書がほかの所で書く順(「{usual}」)と逆です
|
|
23
|
+
en: '"{written}" lists the parties in the reverse of the order this document uses elsewhere ("{usual}")'
|
|
24
|
+
|
|
25
|
+
how_to_fix:
|
|
26
|
+
ja: ほかの所と同じ順に並べ替えてください。順に意味がある(乙を先に立てる)なら、そのままにします。
|
|
27
|
+
en: List the parties in the same order as elsewhere. If the order is meant (the second party first on purpose), leave it.
|
|
28
|
+
|
|
29
|
+
levels: { strict: warning, normal: info, relaxed: info }
|
|
30
|
+
|
|
31
|
+
use_for: [legal, docs, academic, literature, speech]
|
|
32
|
+
off_for:
|
|
33
|
+
docs: "The order of a contract's parties is a contract's convention: the contract genre checks it."
|
|
34
|
+
academic: "The order of a contract's parties is a contract's convention: the contract genre checks it."
|
|
35
|
+
literature: "The order of a contract's parties is a contract's convention: the contract genre checks it."
|
|
36
|
+
speech: "The order of a contract's parties is a contract's convention: the contract genre checks it."
|
|
37
|
+
legal/statute: "A statute defines no parties: it names people by their role."
|
|
38
|
+
legal/judgment: "A court decision quotes each side's words in that side's order."
|
|
39
|
+
legal/patent: "A patent has no parties."
|
|
40
|
+
|
|
41
|
+
group: consistency
|
|
42
|
+
summary:
|
|
43
|
+
ja: 「甲及び乙」と書く契約の中の「乙及び甲」
|
|
44
|
+
en: 乙及び甲 in a contract that otherwise writes 甲及び乙
|
|
45
|
+
example:
|
|
46
|
+
ja:
|
|
47
|
+
config: { genre: legal/contract }
|
|
48
|
+
before: |
|
|
49
|
+
# 業務委託契約書
|
|
50
|
+
|
|
51
|
+
株式会社みなと商会(以下「甲」という。)と株式会社しおさい技研(以下「乙」という。)は、次のとおり契約を結ぶ。
|
|
52
|
+
|
|
53
|
+
第1条 甲又は乙は、相手方が本契約に違反したときは、本契約を解除することができる。
|
|
54
|
+
|
|
55
|
+
第2条 本契約に定めのない事項は、乙及び甲が協議して定める。
|
|
56
|
+
|
|
57
|
+
第3条 本書2通を作成し、甲乙記名押印の上、各1通を保有する。
|
|
58
|
+
after: |
|
|
59
|
+
# 業務委託契約書
|
|
60
|
+
|
|
61
|
+
株式会社みなと商会(以下「甲」という。)と株式会社しおさい技研(以下「乙」という。)は、次のとおり契約を結ぶ。
|
|
62
|
+
|
|
63
|
+
第1条 甲又は乙は、相手方が本契約に違反したときは、本契約を解除することができる。
|
|
64
|
+
|
|
65
|
+
第2条 本契約に定めのない事項は、甲及び乙が協議して定める。
|
|
66
|
+
|
|
67
|
+
第3条 本書2通を作成し、甲乙記名押印の上、各1通を保有する。
|
|
68
|
+
|
|
69
|
+
not_flagged:
|
|
70
|
+
ja: 向きのある言い方(乙から甲への通知、甲に対し乙は、甲が乙に)は、並べたのではなく順に意味があるので数えません。「、」は、あとに並べる語がある並び(甲、乙及び丙)だけをつなぎます。長い語の頭(甲種株式の甲、乙種の乙)は呼び名と読みません。二つの順の数が同じか近い(逆の順が半分より多い)ときは、どちらがふだんの順とも言えないので指しません。呼び名は甲・乙・丙・丁と、文書が括弧で当事者に定めた呼び名(以下「委託者」という)です。契約書のジャンルで、日本語の文書だけで動きます。
|
|
71
|
+
en: A direction (乙から甲への通知, 甲に対し乙は, 甲が乙に) is not a listing and is not counted. A comma joins names only in a listing that goes on to a listing word (甲、乙及び丙). A name that starts a longer word (the 甲 of 甲種株式) is not read. When the two orders are written about as often (the reverse more than half as often), neither is the usual order and nothing is reported. The names are 甲, 乙, 丙 and 丁 and the short names the document gives its parties in brackets. Contracts only, in Japanese documents only.
|
|
72
|
+
rewrite:
|
|
73
|
+
depth: light
|
|
74
|
+
ja:
|
|
75
|
+
direction: 当事者の呼び名を、文書のほかの所と同じ順に並べ替えます。並べた語と、並べる語(及び・又は)は変えません。
|
|
76
|
+
pairs:
|
|
77
|
+
- before: 乙及び甲が協議して定める。
|
|
78
|
+
after: 甲及び乙が協議して定める。
|
|
79
|
+
- before: 乙又は甲は、本契約を解除することができる。
|
|
80
|
+
after: 甲又は乙は、本契約を解除することができる。
|
|
81
|
+
keep:
|
|
82
|
+
- 並べる語(及び・又は・と)
|
|
83
|
+
avoid:
|
|
84
|
+
- 向きのある言い方(乙から甲へ)の順を変える
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
id: required-listed-as-preferred
|
|
2
|
+
layer: L3
|
|
3
|
+
status: stable
|
|
4
|
+
how_to_find: requirement-lists
|
|
5
|
+
# The headings and labels come from requirement-heading; a length of experience is read with requirement-length-unit,
|
|
6
|
+
# requirement-length-word and number-word, and requirement-item-ending is taken off the end of an item.
|
|
7
|
+
languages: [ja, en]
|
|
8
|
+
severity: warning
|
|
9
|
+
|
|
10
|
+
name:
|
|
11
|
+
ja: 必須の条件を歓迎の条件にも挙げている
|
|
12
|
+
en: A required skill listed again as preferred
|
|
13
|
+
|
|
14
|
+
why:
|
|
15
|
+
ja: 求人の「必須」に挙げた資格や経験を「歓迎」にも挙げると、読み手はそれが必ず要るのか、あれば良いだけなのかが分からず、応募するかどうかを決められません。どちらかの項目を書き換えたつもりで、別の行を写したときに起きます。必須(必須スキル、応募資格)と歓迎(歓迎スキル、尚可)の見出しや札の下の箇条書きを比べ、同じ項目が両方にあれば、歓迎のほうを指します。大文字と小文字、全角と半角、記号、項目の終わりの「の方」「こと」は区別しません。経験の長さ(3年以上)は外して比べ、歓迎のほうが長い経験を求めていなければ同じ項目と読みます。
|
|
16
|
+
en: 'When a posting lists a qualification under "Required" and again under "Preferred", the reader cannot tell whether it is a must or only welcome, and cannot decide whether to apply. It happens when one line is copied in place of the one that was meant to be rewritten. The list items under a required heading or label (Required, Requirements, Must have) are compared with those under a preferred one (Preferred, Nice to have, Bonus points), and an item in both is pointed out where it is preferred. Case, full and half width and punctuation are ignored. A length of experience (3+ years) is taken out before comparing, and the two are read as the same item unless the preferred one asks for more experience.'
|
|
17
|
+
|
|
18
|
+
message:
|
|
19
|
+
ja: 「{item}」は必須の条件({required})にも挙げています
|
|
20
|
+
en: '"{item}" is also listed as required ({required})'
|
|
21
|
+
|
|
22
|
+
how_to_fix:
|
|
23
|
+
ja: 必ず要るなら歓迎から消し、あれば良いだけなら必須から消してください。歓迎で上の水準を求めるなら、「5年以上」のように違いを書いてください。
|
|
24
|
+
en: Remove it from the preferred list if it is a must, or from the required list if it is only welcome. If the preferred item asks for more, say how much more, such as "5+ years".
|
|
25
|
+
|
|
26
|
+
# chaff explain と規則の一覧には指摘が無いので、{item} と {required} をこう読ませる。
|
|
27
|
+
placeholders:
|
|
28
|
+
item:
|
|
29
|
+
ja: 〇〇
|
|
30
|
+
en: the item
|
|
31
|
+
required:
|
|
32
|
+
ja: 必須の項目
|
|
33
|
+
en: the required item
|
|
34
|
+
|
|
35
|
+
levels: { strict: error, normal: warning, relaxed: info }
|
|
36
|
+
|
|
37
|
+
use_for: [business, technical, blog, legal, docs, academic, literature, speech]
|
|
38
|
+
off_for:
|
|
39
|
+
literature: "Length, rhythm, repetition and register are the author's choices in literature, and dialogue mixes styles. Only what one work can hold itself to runs: consistent spacing and commas, and numbering without gaps."
|
|
40
|
+
|
|
41
|
+
group: structure
|
|
42
|
+
summary:
|
|
43
|
+
ja: 求人で、必須に挙げた資格や経験を歓迎にも挙げている所
|
|
44
|
+
en: A skill or qualification in a job posting listed both as required and as preferred
|
|
45
|
+
example:
|
|
46
|
+
ja:
|
|
47
|
+
before: |
|
|
48
|
+
# 求人:経理スタッフ
|
|
49
|
+
|
|
50
|
+
## 必須
|
|
51
|
+
|
|
52
|
+
- 日商簿記2級以上
|
|
53
|
+
- 経理の実務経験3年以上
|
|
54
|
+
|
|
55
|
+
## 歓迎
|
|
56
|
+
|
|
57
|
+
- 日商簿記2級以上
|
|
58
|
+
- 英語の書類を読めること
|
|
59
|
+
after: |
|
|
60
|
+
# 求人:経理スタッフ
|
|
61
|
+
|
|
62
|
+
## 必須
|
|
63
|
+
|
|
64
|
+
- 日商簿記2級以上
|
|
65
|
+
- 経理の実務経験3年以上
|
|
66
|
+
|
|
67
|
+
## 歓迎
|
|
68
|
+
|
|
69
|
+
- 連結決算の経験
|
|
70
|
+
- 英語の書類を読めること
|
|
71
|
+
en:
|
|
72
|
+
before: |
|
|
73
|
+
# Job posting: Accounts Assistant
|
|
74
|
+
|
|
75
|
+
## Required
|
|
76
|
+
|
|
77
|
+
- A degree in accounting or finance
|
|
78
|
+
- At least three years of bookkeeping experience
|
|
79
|
+
|
|
80
|
+
## Preferred
|
|
81
|
+
|
|
82
|
+
- At least three years of bookkeeping experience
|
|
83
|
+
- Reading business documents in Japanese
|
|
84
|
+
after: |
|
|
85
|
+
# Job posting: Accounts Assistant
|
|
86
|
+
|
|
87
|
+
## Required
|
|
88
|
+
|
|
89
|
+
- A degree in accounting or finance
|
|
90
|
+
- At least three years of bookkeeping experience
|
|
91
|
+
|
|
92
|
+
## Preferred
|
|
93
|
+
|
|
94
|
+
- Experience with consolidated accounts
|
|
95
|
+
- Reading business documents in Japanese
|
|
96
|
+
|
|
97
|
+
not_flagged:
|
|
98
|
+
ja: 歓迎のほうが長い経験を求める項目(必須「Python」、歓迎「Python 5年以上」)は、上の水準を歓迎していると読んで指しません。違う言葉で同じことを言う項目(「簿記2級」と「日商簿記2級」)、資格の級の違い(「2級以上」と「1級」)、表に書いた条件、一つの行に「必須:Python、Go」のように並べた条件は比べません。見出しや札の無い箇条書きは読みません。
|
|
99
|
+
en: 'A preferred item asking for more experience (required "Python", preferred "5+ years of Python") is read as welcoming a higher level, and is not pointed out. Two items saying the same thing in other words ("bookkeeping" and "accounting"), different grades of a certificate, conditions written in a table, and conditions run together on one line ("Required: Python, Go") are not compared. A list without a required or preferred heading or label is not read.'
|
|
100
|
+
level_meaning:
|
|
101
|
+
ja: 必須と歓迎の両方に同じ項目があるとき指摘
|
|
102
|
+
en: reported when the same item is listed as required and as preferred
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
id: return-type-mismatch
|
|
2
|
+
layer: L1
|
|
3
|
+
status: stable
|
|
4
|
+
how_to_find: return-type
|
|
5
|
+
# The words come from the return-label (Returns, 戻り値), return-verb (returns, 返します), return-subject (は),
|
|
6
|
+
# return-type-word (string, 文字列), return-clause (when, とき) and return-item (per, ごと) lexicons.
|
|
7
|
+
requires: [markdown]
|
|
8
|
+
severity: warning
|
|
9
|
+
|
|
10
|
+
name:
|
|
11
|
+
ja: 戻り値の型が二通りに書かれている
|
|
12
|
+
en: A return type stated two ways
|
|
13
|
+
|
|
14
|
+
why:
|
|
15
|
+
ja: API リファレンスで、関数のシグネチャと、「戻り値:」の行や表や文が違う型を返すと書いていると、読み手はどちらを信じてよいか分からず、返った値を間違った型として扱います。戻り値を変えたときに、片方だけを直すと起きます。コードブロックのシグネチャ(戻り値の型が付いたもの)を基準に、同じ見出しの下の「戻り値:」の行、戻り値の列を持つ表の行、関数名を主語にして「返します」と言う文を、同じ名前の関数どうしで比べます。Array<string> と string[] のような同じ型の別の書き方は同じと読み、「文字列」「数値」のような語は種類として比べます。
|
|
16
|
+
en: When an API reference's signature and a "Returns:" line, a table or a sentence say a function returns different types, the reader cannot tell which to trust and handles the value as the wrong type. It happens when a return type is changed in one place only. The signature in a code block (one with a return type) is the reference; a "Returns:" line under the same heading, a row of a table with a return column, and a sentence whose subject is the function's name and whose verb is "returns" are compared with it, for the same function only. Two spellings of one type (Array<string> and string[]) are the same, and words such as "string" or "number" are compared as kinds of value.
|
|
17
|
+
|
|
18
|
+
message:
|
|
19
|
+
ja: 「{function}」の戻り値が「{stated}」と書かれていますが、シグネチャでは「{signature}」です
|
|
20
|
+
en: "`{function}` is said to return {stated} here, but its signature returns `{signature}`"
|
|
21
|
+
|
|
22
|
+
how_to_fix:
|
|
23
|
+
ja: 実際の戻り値を確かめて、シグネチャか説明のどちらかを直してください。
|
|
24
|
+
en: Check what the function really returns, and correct the signature or the description.
|
|
25
|
+
|
|
26
|
+
levels: { strict: error, normal: warning, relaxed: info }
|
|
27
|
+
|
|
28
|
+
use_for: [technical, docs, blog, legal, academic, literature, speech]
|
|
29
|
+
off_for:
|
|
30
|
+
legal: "Signatures and return types belong to technical writing."
|
|
31
|
+
academic: "Signatures and return types belong to technical writing."
|
|
32
|
+
literature: "Signatures and return types belong to technical writing."
|
|
33
|
+
speech: "Signatures and return types belong to technical writing."
|
|
34
|
+
|
|
35
|
+
group: facts
|
|
36
|
+
summary:
|
|
37
|
+
ja: API リファレンスで、シグネチャと説明が違う戻り値の型を言う所
|
|
38
|
+
en: A return type that the signature and the description state differently
|
|
39
|
+
example:
|
|
40
|
+
ja:
|
|
41
|
+
config: { genre: technical/readme }
|
|
42
|
+
before: |
|
|
43
|
+
## measure
|
|
44
|
+
|
|
45
|
+
```ts
|
|
46
|
+
measure(text: string, tabSize = 4): number
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
measure は、幅を "12" のような文字列で返します。
|
|
50
|
+
after: |
|
|
51
|
+
## measure
|
|
52
|
+
|
|
53
|
+
```ts
|
|
54
|
+
measure(text: string, tabSize = 4): number
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
measure は、幅を桁数の数値で返します。
|
|
58
|
+
en:
|
|
59
|
+
config: { genre: technical/readme }
|
|
60
|
+
before: |
|
|
61
|
+
## measure
|
|
62
|
+
|
|
63
|
+
```ts
|
|
64
|
+
measure(text: string, tabSize = 4): number
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
measure returns the width as a string such as "12".
|
|
68
|
+
after: |
|
|
69
|
+
## measure
|
|
70
|
+
|
|
71
|
+
```ts
|
|
72
|
+
measure(text: string, tabSize = 4): number
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
measure returns the width as a number of columns.
|
|
76
|
+
|
|
77
|
+
not_flagged:
|
|
78
|
+
ja: シグネチャの無い関数、別の名前の関数、引数の違う二つの宣言(オーバーロード。どちらかに合えば合っているとします)、型を読めないもの(オブジェクトの型、括弧で囲んだ型)、型の名前の無い文、二つの種類を言う文(文字列の配列)、条件の節の中の種類の語(「文字列が空のときに true を返します」の文字列)、一つ分を言う語が続く種類の語(「文字列ごとに」の文字列)、null だけを言う文(いつ null が返るかの説明)、ユニオンの一部だけを言うもの(number | null に対する number)、Promise やクラス名のように種類の語では比べられない型は見ません。「返していました」「返しません」のような過去や否定、関数名が主語でない文、名前と動詞の間に語を挟んだ英語の文(measure usually returns)、二つ以上の関数を宣言した見出しの下の「戻り値:」の行、コードブロックの中は読みません。
|
|
79
|
+
en: 'A function with no signature, a different function, two declarations with different parameters (overloads; a statement that fits either is fine), a type that cannot be read (an object type, a type in brackets), a sentence with no type in it, a sentence naming two kinds (an array of strings), a kind word inside a clause about when (string in "returns true when the string is empty"), a kind word naming one item ("one string per line"), a sentence naming only null (when null comes back), part of a union (number against number | null), and a type that a kind word cannot be compared with (Promise, a class name) are not reported. The past (returned), a sentence whose subject is not the function''s name or that puts a word between the name and returns (measure usually returns), a "Returns:" line under a heading that declares two or more functions, and code blocks are not read.'
|
|
@@ -3,7 +3,7 @@ layer: L3
|
|
|
3
3
|
status: stable
|
|
4
4
|
how_to_find: undefined-term
|
|
5
5
|
requires: [structure, pos]
|
|
6
|
-
languages: [ja]
|
|
6
|
+
languages: [ja, en]
|
|
7
7
|
severity: info
|
|
8
8
|
|
|
9
9
|
name:
|
|
@@ -11,8 +11,8 @@ name:
|
|
|
11
11
|
en: A term written like a defined term but defined nowhere
|
|
12
12
|
|
|
13
13
|
why:
|
|
14
|
-
ja:
|
|
15
|
-
en: "A Japanese contract defines its terms with the prefix 本 (本業務, 本サービス). A term in the same form that is never defined (本成果物) leaves the reader asking whether it was defined somewhere and what it means: either a definition is missing, or another defined term was miswritten. Only a document that defines at least one term with that prefix is read, and only the first use of each such term with no definition and no other-prefix form (本件業務) is reported."
|
|
14
|
+
ja: 契約や規約は、「本業務」「本サービス」のように、頭に「本」を付けた語を定義して使います。同じ形の「本成果物」を定義せずに書くと、読み手は、どこかで定義した語なのか、何を指すのかが分かりません。定義が抜けたのか、別の定義した語(本業務)を書き違えたのかのどちらかです。文書が「本」を付けた語を一つ以上定義しているときだけ、その形で書いたのに定義も言い換えの形(本件業務)も無い語を、最初の一か所だけ指します。英語の契約は、定義した語を大文字で始めて書きます(the Services)。"X" means や (the "X") の形の定義が二つ以上ある文書で、文の途中に大文字で始めて書いたのに、どこにも定義が無い語(its Affiliates)も同じく指します。
|
|
15
|
+
en: "A Japanese contract defines its terms with the prefix 本 (本業務, 本サービス). A term in the same form that is never defined (本成果物) leaves the reader asking whether it was defined somewhere and what it means: either a definition is missing, or another defined term was miswritten. Only a document that defines at least one term with that prefix is read, and only the first use of each such term with no definition and no other-prefix form (本件業務) is reported. An English contract capitalises its defined terms (the Services); in a document with at least two definitions written with a defining word (“X” means, (the “X”)), a capitalised term inside a sentence that is defined nowhere (its Affiliates) is reported the same way."
|
|
16
16
|
|
|
17
17
|
message:
|
|
18
18
|
ja: 「{term}」は定義した語の形ですが、この文書のどこにも定義がありません
|
|
@@ -36,8 +36,8 @@ off_for:
|
|
|
36
36
|
|
|
37
37
|
group: consistency
|
|
38
38
|
summary:
|
|
39
|
-
ja:
|
|
40
|
-
en: A
|
|
39
|
+
ja: 「本業務」のように定義した語と同じ形の「本成果物」や、英語の契約で大文字で始めた語(its Affiliates)を、定義しないまま使っている所
|
|
40
|
+
en: A term in the defined form (本成果物, its Affiliates) that the document never defines
|
|
41
41
|
example:
|
|
42
42
|
ja:
|
|
43
43
|
config: { genre: legal/contract }
|
|
@@ -53,10 +53,24 @@ example:
|
|
|
53
53
|
第1条 甲は、ウェブサイトの保守の業務(以下「本業務」という。)を乙に委託する。
|
|
54
54
|
|
|
55
55
|
第2条 乙は、本業務の報告書を甲に提出する。
|
|
56
|
+
en:
|
|
57
|
+
config: { genre: legal/contract }
|
|
58
|
+
before: |
|
|
59
|
+
# Services Agreement
|
|
60
|
+
|
|
61
|
+
This Services Agreement (this "Agreement") is made between Harbour Works Ltd (the "Supplier") and Northwind Retail Inc. (the "Customer").
|
|
62
|
+
|
|
63
|
+
1. The Supplier shall provide the hosting services (the "Services") to the Customer and its Affiliates.
|
|
64
|
+
after: |
|
|
65
|
+
# Services Agreement
|
|
66
|
+
|
|
67
|
+
This Services Agreement (this "Agreement") is made between Harbour Works Ltd (the "Supplier") and Northwind Retail Inc. (the "Customer").
|
|
68
|
+
|
|
69
|
+
1. The Supplier shall provide the hosting services (the "Services") to the Customer.
|
|
56
70
|
|
|
57
71
|
not_flagged:
|
|
58
|
-
ja: 文書が「本」を付けた語を一つも定義していなければ見ません。定義した語とその言い換え(本件業務、defined-term-form が見ます)、定義した語で始まる語(本サービス上)、この文書やその一部を指す語(本条、本項、本書、本契約、本利用規約、本サイト)、どこかでかぎ括弧に入れた語(定義の書き方を chaff
|
|
59
|
-
en: "A document that defines no term with the prefix is not read. Defined terms and their other-prefix forms (本件業務, which defined-term-form reads), terms that start with a defined term (本サービス上), the document's names for itself and its parts (本条, 本項, 本書, 本契約, 本利用規約, 本サイト), terms set in quotes anywhere (a definition chaff could not read), and single words (本人, 本日, 本社) are not reported. Each term is reported once, at its first use. English is
|
|
72
|
+
ja: 文書が「本」を付けた語を一つも定義していなければ見ません。定義した語とその言い換え(本件業務、defined-term-form が見ます)、定義した語で始まる語(本サービス上)、この文書やその一部を指す語(本条、本項、本書、本契約、本利用規約、本サイト)、どこかでかぎ括弧に入れた語(定義の書き方を chaff が読めなかったもの)、一語の言葉(本人、本日、本社)は見ません。同じ語は最初の一か所だけ指します。英語は、大文字は法令・組織・方針の名前(the Federal Trade Commission Act、Acceptable Use Policies)にも使うので、狭く読みます。"X" means や (the "X") の形の定義が二つ未満の文書は見ません。文の頭や記号の後ろの語、小文字の名詞の前に付けた語(the Premium plan)、名前の一覧(proper-name-word、地名、曜日と月、会社の形、当事者の役割)の語を含むもの、条の参照(Section 4、Schedule A)、見出し・リンク・太字・HTML の要素・表・箇条書きの見出しの中、どこかで引用符か太字に入れた語、定義した語を含むもの(大文字小文字を問いません)、文書のどこかで小文字でも書いた語(your Account と your account。普通の語か画面の名前)は見ません。法令・判決・特許は見ません。契約書・規約のジャンルで見ます。
|
|
73
|
+
en: "A document that defines no term with the prefix is not read. Defined terms and their other-prefix forms (本件業務, which defined-term-form reads), terms that start with a defined term (本サービス上), the document's names for itself and its parts (本条, 本項, 本書, 本契約, 本利用規約, 本サイト), terms set in quotes anywhere (a definition chaff could not read), and single words (本人, 本日, 本社) are not reported. Each term is reported once, at its first use. English is read narrowly, since capitals also name laws, organisations and policies (the Federal Trade Commission Act, Acceptable Use Policies): a document with fewer than two definitions written with a defining word (“X” means, (the “X”)) is not read; and a word at the start of a sentence or after a mark, a modifier of a lower-case noun (the Premium plan), a run holding a word of the name lists (proper-name-word, places, weekdays and months, company forms, party roles), a reference to a part (Section 4, Schedule A), anything inside a heading, a link, bold, an HTML element, a table or a list item's label, a word set in quotes or bold anywhere, a run holding a defined term in any case, and a word the document also writes in lower case (your Account and your account: an ordinary word or a screen's name) are not reported. Statutes, court decisions and patents are not read; the contract genre runs this rule."
|
|
60
74
|
rewrite:
|
|
61
75
|
depth: light
|
|
62
76
|
ja:
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import type { Span } from "../plugin.ts";
|
|
2
|
+
import type { Mark } from "../structure/time-marks.ts";
|
|
3
|
+
import { labelOf, readScopes, within, type TimeLength } from "./time-lengths.ts";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* A number of sessions times the length of one against the stated total (90分×15回(計24時間), 15 sessions of 90
|
|
7
|
+
* minutes, 24 hours in total). Read in one line, or in a list whose items say it together, holding exactly one count of
|
|
8
|
+
* two or more sessions, one length of a session and one total. A total written as approximate (約24時間, about 24 hours)
|
|
9
|
+
* may differ by less than the smallest unit it is written in. Pure.
|
|
10
|
+
*/
|
|
11
|
+
export type Count = Span & { readonly amount: number };
|
|
12
|
+
|
|
13
|
+
export type SessionHoursWords = { readonly totals: readonly Mark[]; readonly approximate: readonly Mark[] };
|
|
14
|
+
|
|
15
|
+
export type SessionHoursMismatch = {
|
|
16
|
+
readonly count: Count;
|
|
17
|
+
readonly length: TimeLength;
|
|
18
|
+
readonly total: TimeLength;
|
|
19
|
+
/** The count times the length, in minutes. */
|
|
20
|
+
readonly expected: number;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
const overlaps = (left: Span, right: Span): boolean => left.start < right.end && right.start < left.end;
|
|
24
|
+
|
|
25
|
+
type Statement = { readonly count: Count; readonly length: TimeLength; readonly total: TimeLength };
|
|
26
|
+
|
|
27
|
+
/** The length with a rough mark written before it taken in (計約24時間 reads its label past 約). */
|
|
28
|
+
const withRoughMark = (text: string, length: TimeLength, words: SessionHoursWords): Span => {
|
|
29
|
+
const mark = labelOf(text, length, words.approximate);
|
|
30
|
+
return mark !== undefined && mark.end <= length.start ? { start: mark.start, end: length.end } : length;
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
const statementIn = (text: string, scope: Span, counts: readonly Count[], lengths: readonly TimeLength[], words: SessionHoursWords) => {
|
|
34
|
+
const inCounts = counts.filter((count) => count.amount > 1 && within(count, scope));
|
|
35
|
+
const inLengths = lengths.filter((length) => within(length, scope));
|
|
36
|
+
const totals = inLengths.filter((length) => labelOf(text, withRoughMark(text, length, words), words.totals) !== undefined);
|
|
37
|
+
const sessions = inLengths.filter((length) => !totals.includes(length));
|
|
38
|
+
const [count, length, total] = [inCounts[0], sessions[0], totals[0]];
|
|
39
|
+
if (inCounts.length !== 1 || sessions.length !== 1 || totals.length !== 1 || count === undefined || length === undefined || total === undefined)
|
|
40
|
+
return undefined;
|
|
41
|
+
if (overlaps(count, length) || labelOf(text, length, words.approximate) !== undefined) return undefined;
|
|
42
|
+
return { count, length, total } satisfies Statement;
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
const allowanceOf = (text: string, total: TimeLength, words: SessionHoursWords): number =>
|
|
46
|
+
labelOf(text, total, words.approximate) === undefined ? 0 : total.unit;
|
|
47
|
+
|
|
48
|
+
/** Sessions whose count times length is not the stated total. counts are the written counts of sessions (15回, 15 sessions). */
|
|
49
|
+
export const sessionHoursMismatches = (
|
|
50
|
+
text: string,
|
|
51
|
+
source: string,
|
|
52
|
+
sentences: readonly Span[],
|
|
53
|
+
counts: readonly Count[],
|
|
54
|
+
lengths: readonly TimeLength[],
|
|
55
|
+
words: SessionHoursWords,
|
|
56
|
+
): SessionHoursMismatch[] =>
|
|
57
|
+
readScopes(text, source, { sentences, times: [] }, (scope) => statementIn(text, scope, counts, lengths, words)).flatMap(({ count, length, total }) => {
|
|
58
|
+
const expected = count.amount * length.minutes;
|
|
59
|
+
const allowance = allowanceOf(text, total, words);
|
|
60
|
+
if (allowance === 0 ? expected === total.minutes : Math.abs(expected - total.minutes) < allowance) return [];
|
|
61
|
+
return [{ count, length, total, expected }];
|
|
62
|
+
});
|
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
import type { Span } from "../plugin.ts";
|
|
2
|
+
import { escapeRegExp } from "../orthography.ts";
|
|
3
|
+
import { isWordAt, wordOffsets, type Mark } from "../structure/time-marks.ts";
|
|
4
|
+
import { linesOf } from "../structure/lines.ts";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Lengths of time in hours and minutes (8時間, 7時間30分, 90分, 1-hour, one-hour, 7.5 hours), the words written beside
|
|
8
|
+
* them (実働8時間, 8 hours a day), and the stretches of text a statement of hours is read in. The words come from the
|
|
9
|
+
* language packages; nothing here knows a language. Pure.
|
|
10
|
+
*/
|
|
11
|
+
export type TimeLength = Span & {
|
|
12
|
+
readonly minutes: number;
|
|
13
|
+
/** The smallest unit the length is written in, in minutes: 60 for 24時間, 1 for 90分 or 7時間30分, 30 for 7時間半. */
|
|
14
|
+
readonly unit: number;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export type LengthWords = {
|
|
18
|
+
readonly hourUnits: readonly string[];
|
|
19
|
+
readonly minuteUnits: readonly string[];
|
|
20
|
+
/** What written right after the hour unit adds half an hour (7時間半). */
|
|
21
|
+
readonly halves: readonly string[];
|
|
22
|
+
/** Numbers written as words, in order from one. */
|
|
23
|
+
readonly numberWords: readonly string[];
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
const MINUTES_PER_HOUR = 60;
|
|
27
|
+
const HALF_HOUR = 30;
|
|
28
|
+
|
|
29
|
+
const alternation = (words: readonly string[]): string =>
|
|
30
|
+
words
|
|
31
|
+
.filter((word) => word !== "")
|
|
32
|
+
.toSorted((left, right) => right.length - left.length)
|
|
33
|
+
.map(escapeRegExp)
|
|
34
|
+
.join("|");
|
|
35
|
+
|
|
36
|
+
const LATIN_END = /[a-z]$/iu;
|
|
37
|
+
const LATIN_START = /^[a-z]/iu;
|
|
38
|
+
|
|
39
|
+
const numberOf = (written: string, numberWords: readonly string[]): number => {
|
|
40
|
+
const position = numberWords.findIndex((word) => word.toLowerCase() === written.toLowerCase());
|
|
41
|
+
return position >= 0 ? position + 1 : Number(written.normalize("NFKC"));
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
/** A digit number not inside a longer one, or a number word standing as a word of its own. */
|
|
45
|
+
const numberPattern = (numberWords: readonly string[]): string => {
|
|
46
|
+
const words = alternation(numberWords);
|
|
47
|
+
const digits = "(?<![\\p{N}.,./⁄])(\\p{Nd}+(?:[..]\\p{Nd}+)?)";
|
|
48
|
+
return words === "" ? digits : `(?:${digits}|(?<![\\p{L}\\p{N}\\p{Pd}])(${words}))`;
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
const SEPARATOR = "[\\s\\-‐]?";
|
|
52
|
+
|
|
53
|
+
type Read = { readonly span: Span; readonly minutes: number; readonly unit: number };
|
|
54
|
+
|
|
55
|
+
/** A Latin unit runs on into a longer word ("8 hoursx") or a letter-led tail: not a length. */
|
|
56
|
+
const runsOn = (text: string, end: number): boolean => LATIN_END.test(text.slice(end - 1, end)) && LATIN_START.test(text.charAt(end));
|
|
57
|
+
|
|
58
|
+
const DECIMAL_BASE = 10;
|
|
59
|
+
|
|
60
|
+
/** 7.5 hours is written to a tenth of an hour, 6 minutes. */
|
|
61
|
+
const hourStep = (number: string | undefined): number => {
|
|
62
|
+
const decimals = (number ?? "").normalize("NFKC").split(".")[1]?.length ?? 0;
|
|
63
|
+
return MINUTES_PER_HOUR / DECIMAL_BASE ** decimals;
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
const smallestUnit = (number: string | undefined, minutes: string | undefined, half: string | undefined): number => {
|
|
67
|
+
if (minutes !== undefined) return 1;
|
|
68
|
+
return half === undefined ? hourStep(number) : Math.min(HALF_HOUR, hourStep(number));
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
const hourLengths = (text: string, words: LengthWords): Read[] => {
|
|
72
|
+
const [hours, minutes, halves] = [alternation(words.hourUnits), alternation(words.minuteUnits), alternation(words.halves)];
|
|
73
|
+
if (hours === "") return [];
|
|
74
|
+
const minutePart = minutes === "" ? "" : `|\\s?(\\p{Nd}{1,2})\\s?(?:${minutes})`;
|
|
75
|
+
const halfPart = halves === "" ? "" : `|\\s?(${halves})`;
|
|
76
|
+
const tail = minutePart === "" && halfPart === "" ? "" : `(?:${minutePart.slice(1)}${halfPart})?`;
|
|
77
|
+
const pattern = new RegExp(`${numberPattern(words.numberWords)}${SEPARATOR}(?:${hours})${tail}`, "giu");
|
|
78
|
+
return [...text.matchAll(pattern)].flatMap((match): Read[] => {
|
|
79
|
+
const end = match.index + match[0].length;
|
|
80
|
+
if (runsOn(text, end)) return [];
|
|
81
|
+
const amount = numberOf(match[1] ?? match[2] ?? "", words.numberWords);
|
|
82
|
+
const extra = match[3] === undefined ? 0 : Number(match[3].normalize("NFKC"));
|
|
83
|
+
const half = match[4] === undefined ? 0 : HALF_HOUR;
|
|
84
|
+
return [{ span: { start: match.index, end }, minutes: amount * MINUTES_PER_HOUR + extra + half, unit: smallestUnit(match[1], match[3], match[4]) }];
|
|
85
|
+
});
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
const minuteLengths = (text: string, words: LengthWords): Read[] => {
|
|
89
|
+
const minutes = alternation(words.minuteUnits);
|
|
90
|
+
if (minutes === "") return [];
|
|
91
|
+
const pattern = new RegExp(`${numberPattern(words.numberWords)}${SEPARATOR}(?:${minutes})`, "giu");
|
|
92
|
+
return [...text.matchAll(pattern)].flatMap((match): Read[] => {
|
|
93
|
+
const end = match.index + match[0].length;
|
|
94
|
+
return runsOn(text, end) ? [] : [{ span: { start: match.index, end }, minutes: numberOf(match[1] ?? match[2] ?? "", words.numberWords), unit: 1 }];
|
|
95
|
+
});
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
const overlaps = (left: Span, right: Span): boolean => left.start < right.end && right.start < left.end;
|
|
99
|
+
|
|
100
|
+
/** Lengths of time in hours and minutes, in the order written. A minutes-only reading inside an hours one is dropped. */
|
|
101
|
+
export const timeLengths = (text: string, words: LengthWords): TimeLength[] => {
|
|
102
|
+
const hours = hourLengths(text, words);
|
|
103
|
+
const minutes = minuteLengths(text, words).filter((read) => !hours.some((hour) => overlaps(hour.span, read.span)));
|
|
104
|
+
return [...hours, ...minutes]
|
|
105
|
+
.filter((read) => Number.isFinite(read.minutes) && read.minutes > 0)
|
|
106
|
+
.map((read) => ({ ...read.span, minutes: read.minutes, unit: read.unit }))
|
|
107
|
+
.toSorted((left, right) => left.start - right.start);
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
const longestFirst = (marks: readonly Mark[]): Mark[] => marks.toSorted((left, right) => right.pattern.length - left.pattern.length);
|
|
111
|
+
|
|
112
|
+
const LABEL_GAP_BEFORE = new Set([" ", "\t", "\u3000", ":", ":"]);
|
|
113
|
+
const LABEL_GAP_AFTER = /^\s*/u;
|
|
114
|
+
const LOOK_AROUND = 40;
|
|
115
|
+
|
|
116
|
+
const lower = (text: string): string => text.toLowerCase();
|
|
117
|
+
|
|
118
|
+
/** Where the separators that may stand between a label and its length (休憩:1時間、Break: 1 hour) begin, reading back from at. */
|
|
119
|
+
const gapStart = (text: string, at: number): number => {
|
|
120
|
+
let start = at;
|
|
121
|
+
while (start > 0 && LABEL_GAP_BEFORE.has(text.charAt(start - 1))) start -= 1;
|
|
122
|
+
return start;
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
/** Whether pattern is written as a word at offset at, case aside. Reads a window around it, not the whole text. */
|
|
126
|
+
const isLabelAt = (text: string, at: number, pattern: string): boolean => {
|
|
127
|
+
if (at < 0) return false;
|
|
128
|
+
const from = Math.max(0, at - 1);
|
|
129
|
+
return isWordAt(lower(text.slice(from, at + pattern.length + 1)), at - from, lower(pattern));
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
/** The label written right before span (after separators only), or undefined. */
|
|
133
|
+
const labelBefore = (text: string, span: Span, marks: readonly Mark[]): Span | undefined => {
|
|
134
|
+
const end = gapStart(text, span.start);
|
|
135
|
+
if (span.start - end > LOOK_AROUND) return undefined;
|
|
136
|
+
const found = longestFirst(marks.filter((mark) => (mark.position ?? "before") === "before")).find((mark) =>
|
|
137
|
+
isLabelAt(text, end - mark.pattern.length, mark.pattern),
|
|
138
|
+
);
|
|
139
|
+
return found === undefined ? undefined : { start: end - found.pattern.length, end };
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
/** The label written right after span (after white space only), or undefined. */
|
|
143
|
+
const labelAfter = (text: string, span: Span, marks: readonly Mark[]): Span | undefined => {
|
|
144
|
+
const start = span.end + (LABEL_GAP_AFTER.exec(text.slice(span.end, span.end + LOOK_AROUND))?.[0].length ?? 0);
|
|
145
|
+
const found = longestFirst(marks.filter((mark) => mark.position === "after")).find((mark) => isLabelAt(text, start, mark.pattern));
|
|
146
|
+
return found === undefined ? undefined : { start, end: start + found.pattern.length };
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
/** The label beside span, before or after it. */
|
|
150
|
+
export const labelOf = (text: string, span: Span, marks: readonly Mark[]): Span | undefined => labelBefore(text, span, marks) ?? labelAfter(text, span, marks);
|
|
151
|
+
|
|
152
|
+
/** Where the words of marks are written inside scope. */
|
|
153
|
+
export const mentionsIn = (text: string, scope: Span, marks: readonly Mark[]): Span[] => {
|
|
154
|
+
const inside = lower(text.slice(scope.start, scope.end));
|
|
155
|
+
return [...new Set(marks.map((mark) => lower(mark.pattern)))].flatMap((pattern) =>
|
|
156
|
+
wordOffsets(inside, pattern).map((at) => ({ start: scope.start + at, end: scope.start + at + pattern.length })),
|
|
157
|
+
);
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
export const within = (inner: Span, outer: Span): boolean => inner.start >= outer.start && inner.end <= outer.end;
|
|
161
|
+
|
|
162
|
+
const LIST_ITEM = /^\s*(?:[-*+・•]|\d{1,3}[.)])\s/u;
|
|
163
|
+
|
|
164
|
+
type Paragraph = { readonly span: Span; readonly lines: readonly Span[]; readonly listed: boolean };
|
|
165
|
+
|
|
166
|
+
type Run = { readonly lines: Span[]; listed: boolean };
|
|
167
|
+
|
|
168
|
+
/** Runs of non-blank lines. listed: every line of the run is a list item in the source. */
|
|
169
|
+
const paragraphsOf = (text: string, source: string): Paragraph[] => {
|
|
170
|
+
const listedLines = linesOf(source).map((line) => LIST_ITEM.test(line.text));
|
|
171
|
+
const runs: Run[] = [{ lines: [], listed: true }];
|
|
172
|
+
linesOf(text).forEach((line, index) => {
|
|
173
|
+
const run = runs[runs.length - 1];
|
|
174
|
+
if (line.text.trim() === "") runs.push({ lines: [], listed: true });
|
|
175
|
+
else if (run !== undefined) {
|
|
176
|
+
run.lines.push({ start: line.start, end: line.start + line.text.length });
|
|
177
|
+
run.listed = run.listed && listedLines[index] === true;
|
|
178
|
+
}
|
|
179
|
+
});
|
|
180
|
+
return runs.flatMap(({ lines, listed }) => {
|
|
181
|
+
const [first, last] = [lines[0], lines[lines.length - 1]];
|
|
182
|
+
return first === undefined || last === undefined ? [] : [{ span: { start: first.start, end: last.end }, lines, listed }];
|
|
183
|
+
});
|
|
184
|
+
};
|
|
185
|
+
|
|
186
|
+
const clipped = (inner: Span, outer: Span): Span => ({ start: Math.max(inner.start, outer.start), end: Math.min(inner.end, outer.end) });
|
|
187
|
+
|
|
188
|
+
/** A sentence that ends on a clock time ("5:30 p.m. (a one-hour break") runs on into the next. */
|
|
189
|
+
const joinedAtTimes = (sentences: readonly Span[], times: readonly Span[]): Span[] =>
|
|
190
|
+
sentences.reduce<Span[]>((joined, sentence) => {
|
|
191
|
+
const last = joined[joined.length - 1];
|
|
192
|
+
if (last !== undefined && times.some((time) => time.start < last.end && last.end <= time.end)) {
|
|
193
|
+
joined[joined.length - 1] = { start: last.start, end: sentence.end };
|
|
194
|
+
} else joined.push(sentence);
|
|
195
|
+
return joined;
|
|
196
|
+
}, []);
|
|
197
|
+
|
|
198
|
+
/** The sentences of a line, clipped to it; the line itself when no sentence lies on it. */
|
|
199
|
+
const sentencesOn = (line: Span, sentences: readonly Span[]): Span[] => {
|
|
200
|
+
const on = sentences.filter((sentence) => overlaps(sentence, line)).map((sentence) => clipped(sentence, line));
|
|
201
|
+
return on.length === 0 ? [line] : on;
|
|
202
|
+
};
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* The stretches a statement is read in: each sentence of a line, and a list whose items say it together (勤務時間 /
|
|
206
|
+
* 休憩 / 実働 on three items). read returns undefined when a stretch does not hold exactly one statement; a list that
|
|
207
|
+
* reads as one is not read again line by line.
|
|
208
|
+
*/
|
|
209
|
+
export const readScopes = <T>(
|
|
210
|
+
text: string,
|
|
211
|
+
source: string,
|
|
212
|
+
split: { readonly sentences: readonly Span[]; readonly times: readonly Span[] },
|
|
213
|
+
read: (scope: Span) => T | undefined,
|
|
214
|
+
): T[] => {
|
|
215
|
+
const sentences = joinedAtTimes(split.sentences, split.times);
|
|
216
|
+
return paragraphsOf(text, source).flatMap((paragraph) => {
|
|
217
|
+
const whole = paragraph.listed && paragraph.lines.length > 1 ? read(paragraph.span) : undefined;
|
|
218
|
+
if (whole !== undefined) return [whole];
|
|
219
|
+
return paragraph.lines.flatMap((line) =>
|
|
220
|
+
sentencesOn(line, sentences).flatMap((scope) => {
|
|
221
|
+
const result = read(scope);
|
|
222
|
+
return result === undefined ? [] : [result];
|
|
223
|
+
}),
|
|
224
|
+
);
|
|
225
|
+
});
|
|
226
|
+
};
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { Span } from "../plugin.ts";
|
|
2
|
+
import { escapeRegExp } from "../orthography.ts";
|
|
3
|
+
|
|
4
|
+
export type UnitCount = Span & { readonly amount: number };
|
|
5
|
+
|
|
6
|
+
const LATIN_UNIT = /^[a-z]+$/iu;
|
|
7
|
+
|
|
8
|
+
/** 数字と単位(3泊、3 nights、15回)。英字の単位は語の切れ目まで。Pure. */
|
|
9
|
+
export const numeralCounts = (text: string, units: readonly string[]): UnitCount[] =>
|
|
10
|
+
units.flatMap((unit) => {
|
|
11
|
+
const tail = LATIN_UNIT.test(unit) ? "(?![\\p{L}\\p{N}])" : "";
|
|
12
|
+
const pattern = new RegExp(`(?<![\\p{N}.,])(\\p{Nd}+)\\s?${escapeRegExp(unit)}${tail}`, "giu");
|
|
13
|
+
return [...text.matchAll(pattern)].map((match) => ({
|
|
14
|
+
start: match.index,
|
|
15
|
+
end: match.index + match[0].length,
|
|
16
|
+
amount: Number((match[1] ?? "").normalize("NFKC")),
|
|
17
|
+
}));
|
|
18
|
+
});
|