ilib-lint 2.12.0 → 2.13.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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ilib-lint",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.13.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./src/index.js",
|
|
6
6
|
"module": "./src/index.js",
|
|
@@ -71,10 +71,10 @@
|
|
|
71
71
|
"micromatch": "^4.0.7",
|
|
72
72
|
"options-parser": "^0.4.0",
|
|
73
73
|
"xml-js": "^1.6.11",
|
|
74
|
-
"ilib-common": "^1.1.6",
|
|
75
74
|
"ilib-lint-common": "^3.4.0",
|
|
76
75
|
"ilib-tools-common": "^1.17.0",
|
|
77
|
-
"ilib-locale": "^1.2.4"
|
|
76
|
+
"ilib-locale": "^1.2.4",
|
|
77
|
+
"ilib-common": "^1.1.6"
|
|
78
78
|
},
|
|
79
79
|
"scripts": {
|
|
80
80
|
"coverage": "pnpm test -- --coverage",
|
|
@@ -44,6 +44,7 @@ import ResourceSourceICUUnexplainedParams from '../rules/ResourceSourceICUUnexpl
|
|
|
44
44
|
import ResourceXML from '../rules/ResourceXML.js';
|
|
45
45
|
import ResourceCamelCase from '../rules/ResourceCamelCase.js';
|
|
46
46
|
import ResourceSnakeCase from '../rules/ResourceSnakeCase.js';
|
|
47
|
+
import ResourceKebabCase from '../rules/ResourceKebabCase.js';
|
|
47
48
|
import StringFixer from './string/StringFixer.js';
|
|
48
49
|
import ResourceFixer from './resource/ResourceFixer.js';
|
|
49
50
|
|
|
@@ -271,6 +272,7 @@ export const builtInRulesets = {
|
|
|
271
272
|
"resource-xml": true,
|
|
272
273
|
"resource-snake-case": true,
|
|
273
274
|
"resource-camel-case": true,
|
|
275
|
+
"resource-kebab-case": true,
|
|
274
276
|
|
|
275
277
|
// declarative rules from above
|
|
276
278
|
"resource-url-match": true,
|
|
@@ -365,6 +367,7 @@ class BuiltinPlugin extends Plugin {
|
|
|
365
367
|
ResourceXML,
|
|
366
368
|
ResourceCamelCase,
|
|
367
369
|
ResourceSnakeCase,
|
|
370
|
+
ResourceKebabCase,
|
|
368
371
|
...regexRules
|
|
369
372
|
];
|
|
370
373
|
}
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* ResourceFullwidthPunctuationSubsetFixer.test.js - test the fixer for fullwidth punctuation characters
|
|
3
|
+
*
|
|
4
|
+
* Copyright © 2025 JEDLSoft
|
|
5
|
+
*
|
|
6
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
7
|
+
* you may not use this file except in compliance with the License.
|
|
8
|
+
* You may obtain a copy of the License at
|
|
9
|
+
*
|
|
10
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
11
|
+
*
|
|
12
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
13
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
14
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
15
|
+
*
|
|
16
|
+
* See the License for the specific language governing permissions and
|
|
17
|
+
* limitations under the License.
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
import { ResourceString, ResourcePlural, ResourceArray } from 'ilib-tools-common';
|
|
21
|
+
import { IntermediateRepresentation, SourceFile } from "ilib-lint-common";
|
|
22
|
+
import ResourceFixer from "../../../src/plugins/resource/ResourceFixer.js";
|
|
23
|
+
import { regexRules } from '../../../src/plugins/BuiltinPlugin.js';
|
|
24
|
+
|
|
25
|
+
const rule = regexRules.find(rule => rule.name === "resource-no-fullwidth-punctuation-subset");
|
|
26
|
+
|
|
27
|
+
describe("ResourceFullwidthPunctuationSubsetFixer", () => {
|
|
28
|
+
test("fixes fullwidth punctuation in simple string", () => {
|
|
29
|
+
const fixer = new ResourceFixer();
|
|
30
|
+
const resource = new ResourceString({
|
|
31
|
+
key: "test.key",
|
|
32
|
+
source: "Really? Yes! 100%",
|
|
33
|
+
target: "本当? はい! 100%",
|
|
34
|
+
});
|
|
35
|
+
const source = getSource();
|
|
36
|
+
const representation = getIntermediateRepresentation({ source, resource });
|
|
37
|
+
const fixes = getFixesForStringResource(resource);
|
|
38
|
+
|
|
39
|
+
fixer.applyFixes(representation, fixes);
|
|
40
|
+
|
|
41
|
+
expect(resource.getTarget()).toBe("本当? はい! 100%");
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
test("fixes fullwidth punctuation in plural string", () => {
|
|
45
|
+
const fixer = new ResourceFixer();
|
|
46
|
+
const resource = new ResourcePlural({
|
|
47
|
+
key: "test.key",
|
|
48
|
+
source: {
|
|
49
|
+
one: "Really?",
|
|
50
|
+
other: "Really? Yes! 100%"
|
|
51
|
+
},
|
|
52
|
+
target: {
|
|
53
|
+
other: "本当? はい! 100%"
|
|
54
|
+
},
|
|
55
|
+
});
|
|
56
|
+
const source = getSource()
|
|
57
|
+
const representation = getIntermediateRepresentation({ source, resource })
|
|
58
|
+
const fixes = getFixesForObjectResource(resource);
|
|
59
|
+
|
|
60
|
+
fixer.applyFixes(representation, fixes);
|
|
61
|
+
|
|
62
|
+
expect(resource.getTarget().other).toBe("本当? はい! 100%");
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
test("fixes fullwidth punctuation in array string", () => {
|
|
66
|
+
const fixer = new ResourceFixer();
|
|
67
|
+
const resource = new ResourceArray({
|
|
68
|
+
key: "test.key",
|
|
69
|
+
source: ["Really?", "Yes! 100%"],
|
|
70
|
+
target: ["本当?", "はい! 100%"],
|
|
71
|
+
});
|
|
72
|
+
const source = getSource()
|
|
73
|
+
const representation = getIntermediateRepresentation({ source, resource })
|
|
74
|
+
const fixes = getFixesForArrayResource(resource);
|
|
75
|
+
|
|
76
|
+
fixer.applyFixes(representation, fixes);
|
|
77
|
+
|
|
78
|
+
expect(resource.getTarget()).toEqual(["本当?", "はい! 100%"]);
|
|
79
|
+
});
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
function getIntermediateRepresentation({ source, resource }) {
|
|
84
|
+
return new IntermediateRepresentation({
|
|
85
|
+
sourceFile: source,
|
|
86
|
+
type: "resource",
|
|
87
|
+
ir: [resource],
|
|
88
|
+
dirty: false
|
|
89
|
+
})
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function getSource() {
|
|
93
|
+
return new SourceFile("test.xliff", {
|
|
94
|
+
sourceLocale: "en-US",
|
|
95
|
+
type: "resource"
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function getFixesForStringResource(resource) {
|
|
100
|
+
if (!rule || !rule.fixes) return [];
|
|
101
|
+
return rule.fixes.map(fix => {
|
|
102
|
+
const target = resource.getTarget()
|
|
103
|
+
const fullwidthChar = String.fromCharCode(parseInt(fix.search.slice(2), 16));
|
|
104
|
+
const position = target.indexOf(fullwidthChar);
|
|
105
|
+
const hasFullwidthChar = position !== -1;
|
|
106
|
+
|
|
107
|
+
if (hasFullwidthChar) {
|
|
108
|
+
return ResourceFixer.createFix({
|
|
109
|
+
resource,
|
|
110
|
+
commands: [
|
|
111
|
+
ResourceFixer.createStringCommand(position, 1, fix.replace)
|
|
112
|
+
]
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
return null;
|
|
116
|
+
}).filter(fix => fix !== null);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function getFixesForObjectResource(resource) {
|
|
120
|
+
if (!rule || !rule.fixes) return [];
|
|
121
|
+
return rule.fixes.map(fix => {
|
|
122
|
+
const target = resource.getTarget().other
|
|
123
|
+
const fullwidthChar = String.fromCharCode(parseInt(fix.search.slice(2), 16));
|
|
124
|
+
const position = target.indexOf(fullwidthChar);
|
|
125
|
+
const hasFullwidthChar = position !== -1;
|
|
126
|
+
|
|
127
|
+
if (hasFullwidthChar) {
|
|
128
|
+
return ResourceFixer.createFix({
|
|
129
|
+
resource,
|
|
130
|
+
category: "other",
|
|
131
|
+
commands: [
|
|
132
|
+
ResourceFixer.createStringCommand(position, 1, fix.replace)
|
|
133
|
+
]
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
return null;
|
|
137
|
+
}).filter(fix => fix !== null)
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function getFixesForArrayResource(resource) {
|
|
141
|
+
if (!rule || !rule.fixes) return [];
|
|
142
|
+
const targets = resource.getTarget()
|
|
143
|
+
|
|
144
|
+
return targets.flatMap((target, index) => {
|
|
145
|
+
return rule.fixes.map(fix => {
|
|
146
|
+
const fullwidthChar = String.fromCharCode(parseInt(fix.search.slice(2), 16));
|
|
147
|
+
const position = target.indexOf(fullwidthChar);
|
|
148
|
+
const hasFullwidthChar = position !== -1;
|
|
149
|
+
|
|
150
|
+
if (hasFullwidthChar) {
|
|
151
|
+
return ResourceFixer.createFix({
|
|
152
|
+
resource,
|
|
153
|
+
index,
|
|
154
|
+
commands: [
|
|
155
|
+
ResourceFixer.createStringCommand(position, 1, fix.replace)
|
|
156
|
+
]
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
return null;
|
|
160
|
+
}).filter(fix => fix !== null);
|
|
161
|
+
});
|
|
162
|
+
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import ResourceRule from './ResourceRule.js';
|
|
2
2
|
import {Result} from 'ilib-lint-common';
|
|
3
|
+
import ResourceFixer from '../plugins/resource/ResourceFixer.js';
|
|
3
4
|
|
|
4
5
|
/** @ignore @typedef {import('ilib-tools-common').Resource} Resource */
|
|
5
6
|
|
|
@@ -49,7 +50,7 @@ class ResourceCamelCase extends ResourceRule {
|
|
|
49
50
|
}
|
|
50
51
|
|
|
51
52
|
if (source !== target) {
|
|
52
|
-
|
|
53
|
+
const result = new Result({
|
|
53
54
|
severity: "error",
|
|
54
55
|
id: resource.getKey(),
|
|
55
56
|
source,
|
|
@@ -58,10 +59,29 @@ class ResourceCamelCase extends ResourceRule {
|
|
|
58
59
|
locale: resource.sourceLocale,
|
|
59
60
|
pathName: file,
|
|
60
61
|
highlight: `<e0>${target}</e0>`
|
|
61
|
-
})
|
|
62
|
+
});
|
|
63
|
+
result.fix = this.getFix(resource, source);
|
|
64
|
+
|
|
65
|
+
return result;
|
|
62
66
|
}
|
|
63
67
|
}
|
|
64
68
|
|
|
69
|
+
/**
|
|
70
|
+
* Get the fix for ResourceCamelCase rule
|
|
71
|
+
* @param {Resource} resource The resource to fix
|
|
72
|
+
* @param {string} source The source string that should be used in the target
|
|
73
|
+
* @returns {import('../plugins/resource/ResourceFix.js').default} The fix for ResourceCamelCase rule
|
|
74
|
+
*/
|
|
75
|
+
getFix(resource, source) {
|
|
76
|
+
const command = ResourceFixer.createStringCommand(0, resource.getTarget().length, source);
|
|
77
|
+
|
|
78
|
+
return ResourceFixer.createFix({
|
|
79
|
+
resource,
|
|
80
|
+
target: true,
|
|
81
|
+
commands: [command]
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
|
|
65
85
|
/**
|
|
66
86
|
* @public
|
|
67
87
|
* @param {string} string A non-empty string to check.
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import ResourceRule from './ResourceRule.js';
|
|
2
|
+
import {Result} from 'ilib-lint-common';
|
|
3
|
+
import ResourceFixer from '../plugins/resource/ResourceFixer.js';
|
|
4
|
+
|
|
5
|
+
/** @ignore @typedef {import('ilib-tools-common').Resource} Resource */
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* @classdesc Class representing an ilib-lint programmatic rule for linting kebab cased strings.
|
|
9
|
+
* @class
|
|
10
|
+
*/
|
|
11
|
+
class ResourceKebabCase extends ResourceRule {
|
|
12
|
+
/**
|
|
13
|
+
* Create a ResourceKebabCase rule instance.
|
|
14
|
+
* @param {object} options
|
|
15
|
+
* @param {object} [options.param]
|
|
16
|
+
* @param {string[]} [options.param.except] An array of strings to exclude from the rule.
|
|
17
|
+
*/
|
|
18
|
+
constructor(options) {
|
|
19
|
+
super(options);
|
|
20
|
+
|
|
21
|
+
this.name = "resource-kebab-case";
|
|
22
|
+
this.description = "Ensure that when source strings contain only kebab case and no whitespace, then the targets are the same";
|
|
23
|
+
this.link = "https://gihub.com/iLib-js/ilib-mono/blob/main/packages/ilib-lint/docs/resource-kebab-case.md";
|
|
24
|
+
this.regexps = [
|
|
25
|
+
"^\\s*[a-zA-Z0-9]*(-[a-zA-Z0-9]+)+\\s*$",
|
|
26
|
+
"^\\s*[a-zA-Z0-9]+(-[a-zA-Z0-9]+)*-\\s*$"
|
|
27
|
+
];
|
|
28
|
+
this.exceptions = Array.isArray(options?.param?.except) ? options.param.except : [];
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Check if a source string is in kebab case and if the target string is the same as the source.
|
|
33
|
+
* @override
|
|
34
|
+
* @param {{source: (String|undefined), target: (String|undefined), file: String, resource: Resource}} params
|
|
35
|
+
* @returns {Result|undefined} A Result with severity 'error' if the source string is in kebab case and target string is not the same as the source string, otherwise undefined.
|
|
36
|
+
*/
|
|
37
|
+
matchString({source, target, file, resource}) {
|
|
38
|
+
if (!source || !target) {
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const isException = this.exceptions.includes(source);
|
|
43
|
+
if (isException) {
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const isKebabCase = this.isKebabCase(source);
|
|
48
|
+
if (!isKebabCase) {
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if (source !== target) {
|
|
53
|
+
const result = new Result({
|
|
54
|
+
severity: "error",
|
|
55
|
+
id: resource.getKey(),
|
|
56
|
+
source,
|
|
57
|
+
description: "Do not translate the source string if it consists solely of kebab cased strings and/or digits. Please update the target string so it matches the source string.",
|
|
58
|
+
rule: this,
|
|
59
|
+
locale: resource.sourceLocale,
|
|
60
|
+
pathName: file,
|
|
61
|
+
highlight: `<e0>${target}</e0>`
|
|
62
|
+
});
|
|
63
|
+
result.fix = this.getFix(resource, source);
|
|
64
|
+
return result;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Get the fix for this rule
|
|
70
|
+
* @param {Resource} resource the resource to fix
|
|
71
|
+
* @param {string} source the source string that should be used in the target
|
|
72
|
+
* @returns {import('../plugins/resource/ResourceFix.js').default} the fix for this rule
|
|
73
|
+
*/
|
|
74
|
+
getFix(resource, source) {
|
|
75
|
+
const command = ResourceFixer.createStringCommand(0, resource.getTarget().length, source);
|
|
76
|
+
return ResourceFixer.createFix({
|
|
77
|
+
resource,
|
|
78
|
+
target: true,
|
|
79
|
+
commands: [command]
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* @public
|
|
85
|
+
* @param {string} string A non-empty string to check.
|
|
86
|
+
* @returns {boolean} Returns true for a string that is in kebab case (matches one of the regular expressions declared in the constructor).
|
|
87
|
+
* Otherwise, returns false.
|
|
88
|
+
*/
|
|
89
|
+
isKebabCase(string) {
|
|
90
|
+
const trimmed = string.trim();
|
|
91
|
+
for (const regexp of this.regexps) {
|
|
92
|
+
const match = RegExp(regexp).test(trimmed);
|
|
93
|
+
|
|
94
|
+
if (match) {
|
|
95
|
+
return true;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
return false;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export default ResourceKebabCase;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import ResourceRule from './ResourceRule.js';
|
|
2
2
|
import {Result} from 'ilib-lint-common';
|
|
3
|
+
import ResourceFixer from '../plugins/resource/ResourceFixer.js';
|
|
3
4
|
|
|
4
5
|
/** @ignore @typedef {import('ilib-tools-common').Resource} Resource */
|
|
5
6
|
|
|
@@ -49,7 +50,7 @@ class ResourceSnakeCase extends ResourceRule {
|
|
|
49
50
|
}
|
|
50
51
|
|
|
51
52
|
if (source !== target) {
|
|
52
|
-
|
|
53
|
+
const result = new Result({
|
|
53
54
|
severity: "error",
|
|
54
55
|
id: resource.getKey(),
|
|
55
56
|
source,
|
|
@@ -58,10 +59,29 @@ class ResourceSnakeCase extends ResourceRule {
|
|
|
58
59
|
locale: resource.sourceLocale,
|
|
59
60
|
pathName: file,
|
|
60
61
|
highlight: `<e0>${target}</e0>`
|
|
61
|
-
})
|
|
62
|
+
});
|
|
63
|
+
result.fix = this.getFix(resource, source);
|
|
64
|
+
|
|
65
|
+
return result;
|
|
62
66
|
}
|
|
63
67
|
}
|
|
64
68
|
|
|
69
|
+
/**
|
|
70
|
+
* Get the fix for ResourceSnakeCase rule
|
|
71
|
+
* @param {Resource} resource the resource to fix
|
|
72
|
+
* @param {string} source the source string that should be used in the target
|
|
73
|
+
* @returns {import('../plugins/resource/ResourceFix.js').default} the fix for ResourceSnakeCase rule
|
|
74
|
+
*/
|
|
75
|
+
getFix(resource, source) {
|
|
76
|
+
const command = ResourceFixer.createStringCommand(0, resource.getTarget().length, source);
|
|
77
|
+
|
|
78
|
+
return ResourceFixer.createFix({
|
|
79
|
+
resource,
|
|
80
|
+
target: true,
|
|
81
|
+
commands: [command]
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
|
|
65
85
|
/**
|
|
66
86
|
* @public
|
|
67
87
|
* @param {string} string A non-empty string to check.
|