ilib-lint 2.21.3 → 2.21.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -10
- package/package.json +5 -5
- package/src/index.js +2 -2
- package/src/plugins/BuiltinPlugin.js +1 -1
- package/src/rules/ResourceCamelCase.js +1 -1
- package/src/rules/ResourceKebabCase.js +1 -1
- package/src/rules/ResourceQuoteStyle.js +9 -2
- package/src/rules/ResourceReturnChar.js +1 -1
- package/src/rules/ResourceSentenceEnding.js +72 -14
- package/src/rules/ResourceSnakeCase.js +1 -1
package/README.md
CHANGED
|
@@ -608,17 +608,11 @@ ilib-lint plugins from v1 of ilib-lint to v2.
|
|
|
608
608
|
|
|
609
609
|
## License
|
|
610
610
|
|
|
611
|
-
Copyright © 2022-
|
|
611
|
+
Copyright © 2022-2026, JEDLSoft
|
|
612
612
|
|
|
613
|
-
|
|
614
|
-
you may not use this file except in compliance with the License.
|
|
615
|
-
You may obtain a copy of the License at
|
|
613
|
+
This package is released under the [Apache License, Version 2.0](https://www.apache.org/licenses/LICENSE-2.0). The full license text is available in the [LICENSE](https://github.com/iLib-js/ilib-mono/blob/main/packages/ilib-lint/LICENSE) file in the ilib-mono repository on GitHub.
|
|
616
614
|
|
|
617
|
-
|
|
615
|
+
## Release Notes
|
|
618
616
|
|
|
619
|
-
|
|
620
|
-
distributed under the License is distributed on an "AS IS" BASIS,
|
|
621
|
-
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
617
|
+
See [CHANGELOG.md](https://github.com/iLib-js/ilib-mono/blob/main/packages/ilib-lint/CHANGELOG.md).
|
|
622
618
|
|
|
623
|
-
See the License for the specific language governing permissions and
|
|
624
|
-
limitations under the License.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ilib-lint",
|
|
3
|
-
"version": "2.21.
|
|
3
|
+
"version": "2.21.5",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./src/index.js",
|
|
6
6
|
"module": "./src/index.js",
|
|
@@ -77,15 +77,15 @@
|
|
|
77
77
|
"options-parser": "^0.4.0",
|
|
78
78
|
"xml-js": "^1.6.11",
|
|
79
79
|
"ilib-xliff-webos": "^1.0.11",
|
|
80
|
-
"ilib-casemapper": "^1.0.2",
|
|
81
80
|
"ilib-common": "^1.1.7",
|
|
81
|
+
"ilib-casemapper": "^1.0.2",
|
|
82
82
|
"ilib-ctype": "^1.3.0",
|
|
83
83
|
"ilib-lint-common": "^3.7.0",
|
|
84
84
|
"ilib-locale": "^1.4.0",
|
|
85
|
-
"ilib-localematcher": "^1.3.
|
|
85
|
+
"ilib-localematcher": "^1.3.4",
|
|
86
86
|
"ilib-scriptinfo": "^1.0.0",
|
|
87
|
-
"ilib-
|
|
88
|
-
"ilib-
|
|
87
|
+
"ilib-xliff": "^1.4.1",
|
|
88
|
+
"ilib-tools-common": "^1.22.0"
|
|
89
89
|
},
|
|
90
90
|
"scripts": {
|
|
91
91
|
"coverage": "LANG=en_US.UTF8 node --trace-warnings --experimental-vm-modules node_modules/jest/bin/jest.js --coverage",
|
package/src/index.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
/*
|
|
3
3
|
* index.js - main program of ilib-lint
|
|
4
4
|
*
|
|
5
|
-
* Copyright © 2022-
|
|
5
|
+
* Copyright © 2022-2026 JEDLSoft
|
|
6
6
|
*
|
|
7
7
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
8
8
|
* you may not use this file except in compliance with the License.
|
|
@@ -178,7 +178,7 @@ if (options.opt.quiet) {
|
|
|
178
178
|
logger.level = "debug";
|
|
179
179
|
}
|
|
180
180
|
|
|
181
|
-
logger.info("ilib-lint - Copyright (c) 2022-
|
|
181
|
+
logger.info("ilib-lint - Copyright (c) 2022-2026 JEDLsoft, All rights reserved.");
|
|
182
182
|
|
|
183
183
|
let paths = options.args;
|
|
184
184
|
if (paths.length === 0) {
|
|
@@ -169,7 +169,7 @@ export const regexRules = [
|
|
|
169
169
|
regexps: [
|
|
170
170
|
"(\\p{L}+('\\p{L}+)+)" // word boundary + word chars + quote + word chars + word boundary (e.g., it's, don't, d'l'homme)
|
|
171
171
|
],
|
|
172
|
-
link: "https://github.com/iLib-js/ilib-
|
|
172
|
+
link: "https://github.com/iLib-js/ilib-mono/blob/main/packages/ilib-lint/docs/resource-apostrophe.md",
|
|
173
173
|
fixes: [
|
|
174
174
|
{ search: "'", replace: "\u2019" }
|
|
175
175
|
]
|
|
@@ -39,7 +39,7 @@ class ResourceCamelCase extends ResourceRule {
|
|
|
39
39
|
|
|
40
40
|
this.name = "resource-camel-case";
|
|
41
41
|
this.description = "Ensure that when source strings contain only camel case and no whitespace, then the targets are the same";
|
|
42
|
-
this.link = "https://
|
|
42
|
+
this.link = "https://github.com/iLib-js/ilib-mono/blob/main/packages/ilib-lint/docs/resource-camel-case.md";
|
|
43
43
|
this.regexps = [
|
|
44
44
|
"^\\s*[a-z\\d]+([A-Z][a-z\\d]+)+\\s*$",
|
|
45
45
|
"^\\s*[A-Z][a-z\\d]+([A-Z][a-z\\d]+)+\\s*$",
|
|
@@ -19,7 +19,7 @@ class ResourceKebabCase extends ResourceRule {
|
|
|
19
19
|
|
|
20
20
|
this.name = "resource-kebab-case";
|
|
21
21
|
this.description = "Ensure that when source strings contain only kebab case and no whitespace, then the targets are the same";
|
|
22
|
-
this.link = "https://
|
|
22
|
+
this.link = "https://github.com/iLib-js/ilib-mono/blob/main/packages/ilib-lint/docs/resource-kebab-case.md";
|
|
23
23
|
|
|
24
24
|
const param = this.getParam() || {};
|
|
25
25
|
this.exceptions = Array.isArray(param?.except) ? param.except : [];
|
|
@@ -236,14 +236,17 @@ class ResourceQuoteStyle extends ResourceRule {
|
|
|
236
236
|
}
|
|
237
237
|
let match;
|
|
238
238
|
let commands = [];
|
|
239
|
+
const startQuotePositions = new Set();
|
|
239
240
|
|
|
240
241
|
while ((match = startQuote.exec(tar)) !== null) {
|
|
241
242
|
// now that we have found a start quote, find it in the matched string so
|
|
242
243
|
// that we can get the index into that string
|
|
243
244
|
const offset = match[0].indexOf(match[3]);
|
|
245
|
+
const pos = match.index + offset;
|
|
246
|
+
startQuotePositions.add(pos);
|
|
244
247
|
|
|
245
248
|
commands.push(ResourceFixer.createStringCommand(
|
|
246
|
-
|
|
249
|
+
pos,
|
|
247
250
|
1,
|
|
248
251
|
correctQuoteStart
|
|
249
252
|
));
|
|
@@ -253,9 +256,13 @@ class ResourceQuoteStyle extends ResourceRule {
|
|
|
253
256
|
// now that we have found an end quote, find it in the matched string so
|
|
254
257
|
// that we can get the index into that string
|
|
255
258
|
const offset = match[0].indexOf(match[3]);
|
|
259
|
+
const pos = match.index + offset;
|
|
260
|
+
// skip positions already handled by startQuote to avoid overlapping commands
|
|
261
|
+
// (can occur when a quote char is surrounded by CJK letters on both sides)
|
|
262
|
+
if (startQuotePositions.has(pos)) continue;
|
|
256
263
|
|
|
257
264
|
commands.push(ResourceFixer.createStringCommand(
|
|
258
|
-
|
|
265
|
+
pos,
|
|
259
266
|
1,
|
|
260
267
|
correctQuoteEnd
|
|
261
268
|
));
|
|
@@ -34,7 +34,7 @@ export default class ResourceReturnChar extends ResourceRule {
|
|
|
34
34
|
super(options);
|
|
35
35
|
this.name = "resource-return-char";
|
|
36
36
|
this.description = "Checks that the number of return characters (CR, LF, CRLF) in the source matches the target";
|
|
37
|
-
this.link = "https://github.com/iLib-js/ilib-
|
|
37
|
+
this.link = "https://github.com/iLib-js/ilib-mono/blob/main/packages/ilib-lint/docs/resource-return-char.md";
|
|
38
38
|
this.type = "resource";
|
|
39
39
|
}
|
|
40
40
|
|
|
@@ -102,9 +102,11 @@ const punctuationMap = {
|
|
|
102
102
|
|
|
103
103
|
/**
|
|
104
104
|
* @ignore
|
|
105
|
-
* @typedef {{minimumLength?: number}} ResourceSentenceEndingFixedOptions
|
|
105
|
+
* @typedef {{minimumLength?: number, exceptions?: string[]}} ResourceSentenceEndingFixedOptions
|
|
106
106
|
* @property {number} [minimumLength=10] - Minimum length of source string before the rule is applied.
|
|
107
107
|
* Strings shorter than this length will be skipped (useful for avoiding false positives on abbreviations).
|
|
108
|
+
* @property {string[]} [exceptions] - Array of source strings to skip checking for ALL locales.
|
|
109
|
+
* Useful for handling special cases that should be globally excluded from sentence-ending punctuation checks.
|
|
108
110
|
*/
|
|
109
111
|
|
|
110
112
|
/**
|
|
@@ -120,7 +122,7 @@ class ResourceSentenceEnding extends ResourceRule {
|
|
|
120
122
|
/**
|
|
121
123
|
* Constructs a new ResourceSentenceEnding rule instance.
|
|
122
124
|
*
|
|
123
|
-
* @param {ResourceSentenceEndingOptions} [options] - Configuration options for the rule
|
|
125
|
+
* @param {{ param?: ResourceSentenceEndingOptions, sourceLocale?: string, getLogger?: Function }} [options] - Configuration options for the rule
|
|
124
126
|
*
|
|
125
127
|
* @example
|
|
126
128
|
* // Basic usage with default settings
|
|
@@ -173,7 +175,7 @@ class ResourceSentenceEnding extends ResourceRule {
|
|
|
173
175
|
super(options);
|
|
174
176
|
this.name = "resource-sentence-ending";
|
|
175
177
|
this.description = "Checks that sentence-ending punctuation is appropriate for the locale of the target string and matches the punctuation in the source string";
|
|
176
|
-
this.link = "https://github.com/iLib-js/ilib-
|
|
178
|
+
this.link = "https://github.com/iLib-js/ilib-mono/blob/main/packages/ilib-lint/docs/resource-sentence-ending.md";
|
|
177
179
|
|
|
178
180
|
// Get the parameter from the options
|
|
179
181
|
const param = this.getParam() || {};
|
|
@@ -181,9 +183,16 @@ class ResourceSentenceEnding extends ResourceRule {
|
|
|
181
183
|
// Initialize minimum length configuration
|
|
182
184
|
this.minimumLength = Math.max(0, param?.minimumLength ?? 10);
|
|
183
185
|
|
|
186
|
+
// Initialize global exceptions (apply to all locales), deduplicated via Set
|
|
187
|
+
this.globalExceptions = new Set(
|
|
188
|
+
Array.isArray(param?.exceptions)
|
|
189
|
+
? param.exceptions.map((/** @type {string} */ e) => e.toLowerCase().trim())
|
|
190
|
+
: []
|
|
191
|
+
);
|
|
192
|
+
|
|
184
193
|
// Initialize custom punctuation mappings from configuration
|
|
185
194
|
this.customPunctuationMap = {};
|
|
186
|
-
// Initialize exception lists from configuration
|
|
195
|
+
// Initialize locale-specific exception lists from configuration
|
|
187
196
|
this.exceptionsMap = {};
|
|
188
197
|
|
|
189
198
|
if (param && typeof param === 'object' && !Array.isArray(param)) {
|
|
@@ -211,9 +220,11 @@ class ResourceSentenceEnding extends ResourceRule {
|
|
|
211
220
|
...punctuationMappings
|
|
212
221
|
};
|
|
213
222
|
|
|
214
|
-
// Store exceptions
|
|
223
|
+
// Store exceptions as a Set to deduplicate
|
|
215
224
|
if (exceptions && Array.isArray(exceptions)) {
|
|
216
|
-
this.exceptionsMap[language] =
|
|
225
|
+
this.exceptionsMap[language] = new Set(
|
|
226
|
+
exceptions.map((/** @type {string} */ e) => e.toLowerCase().trim())
|
|
227
|
+
);
|
|
217
228
|
}
|
|
218
229
|
}
|
|
219
230
|
}
|
|
@@ -438,6 +449,40 @@ class ResourceSentenceEnding extends ResourceRule {
|
|
|
438
449
|
return stripped.endsWith(expected);
|
|
439
450
|
}
|
|
440
451
|
|
|
452
|
+
/**
|
|
453
|
+
* Detect whether a string uses "person quotation" style, where the quoted
|
|
454
|
+
* content is a direct speech quotation introduced by a comma.
|
|
455
|
+
*
|
|
456
|
+
* Person quotation: She said, "A quotation!"
|
|
457
|
+
* Call-out (default): select 'Manual Zoom.'
|
|
458
|
+
*
|
|
459
|
+
* @param {string} str
|
|
460
|
+
* @returns {boolean}
|
|
461
|
+
*/
|
|
462
|
+
static isPersonQuotation(str) {
|
|
463
|
+
if (!str) return false;
|
|
464
|
+
const trimmed = str.trim();
|
|
465
|
+
const quoteChars = ResourceSentenceEnding.allQuoteChars;
|
|
466
|
+
|
|
467
|
+
const lastChar = trimmed.charAt(trimmed.length - 1);
|
|
468
|
+
if (!quoteChars.includes(lastChar)) return false;
|
|
469
|
+
|
|
470
|
+
let openPos = -1;
|
|
471
|
+
for (let i = trimmed.length - 2; i >= 0; i--) {
|
|
472
|
+
if (quoteChars.includes(trimmed.charAt(i))) {
|
|
473
|
+
openPos = i;
|
|
474
|
+
break;
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
if (openPos <= 0) return false;
|
|
478
|
+
|
|
479
|
+
let beforeQuote = openPos - 1;
|
|
480
|
+
while (beforeQuote >= 0 && /\s/.test(trimmed.charAt(beforeQuote))) {
|
|
481
|
+
beforeQuote--;
|
|
482
|
+
}
|
|
483
|
+
return beforeQuote >= 0 && trimmed.charAt(beforeQuote) === ',';
|
|
484
|
+
}
|
|
485
|
+
|
|
441
486
|
/**
|
|
442
487
|
* Get the last quoted string in the input, or null if none found.
|
|
443
488
|
* Handles all quote types in allQuoteChars.
|
|
@@ -948,12 +993,17 @@ class ResourceSentenceEnding extends ResourceRule {
|
|
|
948
993
|
}
|
|
949
994
|
}
|
|
950
995
|
|
|
951
|
-
|
|
996
|
+
const normalizedSource = source.toLowerCase().trim();
|
|
997
|
+
|
|
998
|
+
// Exception 3: Check if source is in global exception list (all locales)
|
|
999
|
+
if (this.globalExceptions.has(normalizedSource)) {
|
|
1000
|
+
return undefined;
|
|
1001
|
+
}
|
|
1002
|
+
|
|
1003
|
+
// Exception 4: Check if source is in locale-specific exception list
|
|
952
1004
|
const exceptions = this.exceptionsMap[targetLanguage];
|
|
953
|
-
if (exceptions) {
|
|
954
|
-
|
|
955
|
-
return undefined;
|
|
956
|
-
}
|
|
1005
|
+
if (exceptions?.has(normalizedSource)) {
|
|
1006
|
+
return undefined;
|
|
957
1007
|
}
|
|
958
1008
|
|
|
959
1009
|
const optionalPunctuationLanguages = ['th', 'lo', 'my', 'km', 'vi', 'id', 'ms', 'tl', 'jv', 'su'];
|
|
@@ -974,12 +1024,20 @@ class ResourceSentenceEnding extends ResourceRule {
|
|
|
974
1024
|
let highlight = '';
|
|
975
1025
|
let description = '';
|
|
976
1026
|
|
|
1027
|
+
const targetTrimmed = target?.trim() || '';
|
|
1028
|
+
const targetEndsWithQuote = quoteChars.includes(targetTrimmed.charAt(targetTrimmed.length - 1));
|
|
1029
|
+
|
|
977
1030
|
let lastSentence;
|
|
978
|
-
if (sourceEndsWithQuote
|
|
979
|
-
|
|
1031
|
+
if (sourceEndsWithQuote &&
|
|
1032
|
+
(ResourceSentenceEnding.isPersonQuotation(sourceTrimmed) || targetEndsWithQuote)) {
|
|
1033
|
+
// Person quotation (e.g. She said, "Hello!") or both source and target
|
|
1034
|
+
// end with a quote — compare quoted content
|
|
980
1035
|
lastSentence = ResourceSentenceEnding.getLastQuotedString(target) || target.trim();
|
|
1036
|
+
} else if (sourceEndsWithQuote) {
|
|
1037
|
+
// Call-out / reference where the target does not end with a quote —
|
|
1038
|
+
// compare overall target ending, not a sentence fragment
|
|
1039
|
+
lastSentence = target.trim();
|
|
981
1040
|
} else {
|
|
982
|
-
// Use the full target string
|
|
983
1041
|
lastSentence = this.getLastSentenceFromContent(target, targetLocaleObj);
|
|
984
1042
|
}
|
|
985
1043
|
|
|
@@ -39,7 +39,7 @@ class ResourceSnakeCase extends ResourceRule {
|
|
|
39
39
|
|
|
40
40
|
this.name = "resource-snake-case";
|
|
41
41
|
this.description = "Ensure that when source strings contain only snake case and no whitespace, then the targets are the same";
|
|
42
|
-
this.link = "https://
|
|
42
|
+
this.link = "https://github.com/iLib-js/ilib-mono/blob/main/packages/ilib-lint/docs/resource-snake-case.md";
|
|
43
43
|
this.regexps = [
|
|
44
44
|
"^\\s*[a-zA-Z0-9]*(_[a-zA-Z0-9]+)+\\s*$",
|
|
45
45
|
"^\\s*[a-zA-Z0-9]+(_[a-zA-Z0-9]+)*_\\s*$"
|