ilib-lint 2.7.2 → 2.8.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/src/index.js CHANGED
@@ -2,7 +2,7 @@
2
2
  /*
3
3
  * index.js - main program of ilib-lint
4
4
  *
5
- * Copyright © 2022-2024 JEDLSoft
5
+ * Copyright © 2022-2025 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.
@@ -74,7 +74,7 @@ const optionConfig = {
74
74
  },
75
75
  list: {
76
76
  flag: true,
77
- help: "Load all plugins, and then list out all available parsers, rules, rulesets, and formatters, then exit."
77
+ help: "Load all plugins, and then list out all available parsers, rules, rulesets, formatters, transformers, and serializers then exit."
78
78
  },
79
79
  locales: {
80
80
  short: "l",
@@ -103,10 +103,20 @@ const optionConfig = {
103
103
  flag: true,
104
104
  help: "Produce lots of progress output during the run."
105
105
  },
106
+ write: {
107
+ flag: true,
108
+ default: false,
109
+ help: "If a file is changed by a fix or transformer, write the file to disk again."
110
+ },
106
111
  fix: {
107
112
  flag: true,
108
113
  "default": false,
109
- help: "If auto-fixes are available for some of the errors, apply them (overwriting the original file)."
114
+ help: "If auto-fixes are available for some of the errors, apply them and write the file back to disk again. Implies --write."
115
+ },
116
+ overwrite: {
117
+ flag: true,
118
+ "default": false,
119
+ help: "When writing a modified file to disk, overwrite the original file instead of writing to a new file."
110
120
  },
111
121
  "max-errors": {
112
122
  short: "me",
@@ -169,7 +179,7 @@ if (options.opt.quiet) {
169
179
  logger.level = "debug";
170
180
  }
171
181
 
172
- logger.info("ilib-lint - Copyright (c) 2022-2024 JEDLsoft, All rights reserved.");
182
+ logger.info("ilib-lint - Copyright (c) 2022-2025 JEDLsoft, All rights reserved.");
173
183
 
174
184
  let paths = options.args;
175
185
  if (paths.length === 0) {
@@ -188,6 +198,12 @@ options.opt.locales = options.opt.locales.map(spec => {
188
198
  return loc.getSpec();
189
199
  });
190
200
 
201
+ if (options.opt["auto-fix"] || options.opt.overwrite) {
202
+ // The write option indicates that modified files should be written back to disk.
203
+ // The write option is implicit if either auto-fix or overwrite is set.
204
+ options.opt.write = true;
205
+ }
206
+
191
207
  // Load configuration
192
208
  let config;
193
209
  const cwdConfigProvider = new FolderConfigurationProvider(".");
@@ -226,6 +242,10 @@ try {
226
242
  const ruleSetDefinitions = ruleMgr.getRuleSetDefinitions();
227
243
  const parserMgr = pluginMgr.getParserManager();
228
244
  const parserDescriptions = parserMgr.getDescriptions();
245
+ const transformerMgr = pluginMgr.getTransformerManager();
246
+ const transformerDescriptions = transformerMgr.getDescriptions();
247
+ const serializerMgr = pluginMgr.getSerializerManager();
248
+ const serializerDescriptions = serializerMgr.getDescriptions();
229
249
  const formatterMgr = pluginMgr.getFormatterManager();
230
250
  const formatterDescriptions = formatterMgr.getDescriptions();
231
251
 
@@ -251,11 +271,32 @@ try {
251
271
  for (name in ruleSetDefinitions) {
252
272
  output = output.concat(indent(wrap(`${name} - ${ruleSetDefinitions[name].join(", ")}`, 76, " "), 2));
253
273
  }
254
- output.push("");
255
274
 
256
- output.push("Formatters:");
257
- for (name in formatterDescriptions) {
258
- output = output.concat(indent(wrap(`${name} - ${formatterDescriptions[name]}`, 76, " "), 2));
275
+ if (Object.keys(formatterDescriptions).length > 0) {
276
+ output.push("");
277
+
278
+ output.push("Formatters:");
279
+ for (name in formatterDescriptions) {
280
+ output = output.concat(indent(wrap(`${name} - ${formatterDescriptions[name]}`, 76, " "), 2));
281
+ }
282
+ }
283
+
284
+ if (Object.keys(transformerDescriptions).length > 0) {
285
+ output.push("");
286
+
287
+ output.push("Transformers:");
288
+ for (name in transformerDescriptions) {
289
+ output = output.concat(indent(wrap(`${name} - ${transformerDescriptions[name]}`, 76, " "), 2));
290
+ }
291
+ }
292
+
293
+ if (Object.keys(serializerDescriptions).length > 0) {
294
+ output.push("");
295
+
296
+ output.push("Serializers:");
297
+ for (name in serializerDescriptions) {
298
+ output = output.concat(indent(wrap(`${name} - ${serializerDescriptions[name]}`, 76, " "), 2));
299
+ }
259
300
  }
260
301
 
261
302
  console.log(output.join('\n'));
@@ -24,6 +24,7 @@ import XliffSerializer from './XliffSerializer.js';
24
24
  import LineParser from './LineParser.js';
25
25
  import LineSerializer from './LineSerializer.js';
26
26
  import StringParser from './string/StringParser.js';
27
+ import ErrorFilterTransformer from './ErrorFilterTransformer.js';
27
28
  import StringSerializer from './string/StringSerializer.js';
28
29
  import AnsiConsoleFormatter from '../formatters/AnsiConsoleFormatter.js';
29
30
  import ResourceICUPlurals from '../rules/ResourceICUPlurals.js';
@@ -301,11 +302,22 @@ class BuiltinPlugin extends Plugin {
301
302
  return [XliffParser, LineParser, StringParser];
302
303
  }
303
304
 
305
+ /**
306
+ * For a "transformer" type of plugin, this returns a list of Transformer classes
307
+ * that this plugin implements.
308
+ *
309
+ * @returns {Array.<Transformer>} list of Transformer classes implemented by this
310
+ * plugin
311
+ */
312
+ getTransformers() {
313
+ return [ErrorFilterTransformer];
314
+ }
315
+
304
316
  /**
305
317
  * For a "serializer" type of plugin, this returns a list of Serializer classes
306
318
  * that this plugin implements.
307
319
  *
308
- * @returns {Array.<Parser>} list of Serializer classes implemented by this
320
+ * @returns {Array.<Serializer>} list of Serializer classes implemented by this
309
321
  * plugin
310
322
  */
311
323
  getSerializers() {
@@ -0,0 +1,59 @@
1
+ /*
2
+ * ErrorFilterTransformer - transform an intermediate representation
3
+ * by filtering out errors
4
+ *
5
+ * Copyright © 2025 JEDLSoft
6
+ *
7
+ * Licensed under the Apache License, Version 2.0 (the "License");
8
+ * you may not use this file except in compliance with the License.
9
+ * You may obtain a copy of the License at
10
+ *
11
+ * http://www.apache.org/licenses/LICENSE-2.0
12
+ *
13
+ * Unless required by applicable law or agreed to in writing, software
14
+ * distributed under the License is distributed on an "AS IS" BASIS,
15
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16
+ *
17
+ * See the License for the specific language governing permissions and
18
+ * limitations under the License.
19
+ */
20
+
21
+ import { IntermediateRepresentation, Transformer, Result } from 'ilib-lint-common';
22
+
23
+ /**
24
+ * Filter out errors from the intermediate representation.
25
+ */
26
+ class ErrorFilterTransformer extends Transformer {
27
+ /**
28
+ * Create a new transformer instance.
29
+ */
30
+ constructor(options) {
31
+ super(options);
32
+ this.name = "errorfilter";
33
+ this.description = "Filter out translation units that have errors from the resources representation.";
34
+ this.type = "resource";
35
+ }
36
+
37
+ /**
38
+ * Filter out errors from the intermediate representation.
39
+ *
40
+ * @param {IntermediateRepresentation} ir the intermediate representation to filter
41
+ * @param {Result[]|undefined} results the results of the linting process
42
+ * @returns {IntermediateRepresentation} the filtered intermediate representation
43
+ */
44
+ transform(ir, results) {
45
+ if (ir.getType() !== 'resource' || !results) {
46
+ return ir;
47
+ }
48
+ const resources = ir.getRepresentation();
49
+ const idsToExclude = results.filter(result => result.id && result.severity === 'error').map(result => result.id);
50
+ const filteredResources = resources.filter(resource => !idsToExclude.includes(resource.getKey()));
51
+ return new IntermediateRepresentation({
52
+ type: ir.getType(),
53
+ ir: filteredResources,
54
+ sourceFile: ir.getSourceFile()
55
+ });
56
+ }
57
+ };
58
+
59
+ export default ErrorFilterTransformer;
@@ -1,7 +1,7 @@
1
1
  /*
2
2
  * LineSerializer.js - Serializer for plain text files
3
3
  *
4
- * Copyright © 2024 JEDLSoft
4
+ * Copyright © 2024-2025 JEDLSoft
5
5
  *
6
6
  * Licensed under the Apache License, Version 2.0 (the "License");
7
7
  * you may not use this file except in compliance with the License.
@@ -30,7 +30,7 @@ class LineSerializer extends Serializer {
30
30
  constructor(options) {
31
31
  super(options);
32
32
  this.name = "line";
33
- this.description = "A serializer for plain text files that splits them into lines.";
33
+ this.description = "A serializer for plain text files that joins an array of lines into a file with newlines.";
34
34
  this.type = "line";
35
35
  }
36
36
 
@@ -38,11 +38,13 @@ class LineSerializer extends Serializer {
38
38
  * Convert the intermediate representation back into a source file.
39
39
  *
40
40
  * @override
41
- * @param {IntermediateRepresentation} ir the intermediate representation to convert
41
+ * @param {IntermediateRepresentation[]} irs the intermediate representations to convert
42
42
  * @returns {SourceFile} the source file with the contents of the intermediate
43
43
  * representation
44
44
  */
45
- serialize(ir) {
45
+ serialize(irs) {
46
+ // should only be one ir in this array
47
+ const ir = irs[0];
46
48
  const lines = ir.getRepresentation();
47
49
  const data = lines.join("\n");
48
50
  return new SourceFile(ir.sourceFile.getPath(), {
@@ -33,10 +33,6 @@ class XliffParser extends Parser {
33
33
  this.extensions = [ "xliff", "xlif", "xlf" ];
34
34
  this.name = "xliff";
35
35
  this.description = "A parser for xliff files. This can handle xliff v1.2 and v2.0 format files.";
36
-
37
- // indicate that this parser can write out the intermediate representation if it has been modified
38
- // by the linter through a Fixer or by a transformer
39
- this.canWrite = true;
40
36
  }
41
37
 
42
38
  /**
@@ -39,11 +39,13 @@ class XliffSerializer extends Serializer {
39
39
  * Convert the intermediate representation back into a source file.
40
40
  *
41
41
  * @override
42
- * @param {IntermediateRepresentation} ir the intermediate representation to convert
42
+ * @param {IntermediateRepresentation[]} irs the intermediate representations to convert
43
43
  * @returns {SourceFile} the source file with the contents of the intermediate
44
44
  * representation
45
45
  */
46
- serialize(ir) {
46
+ serialize(irs) {
47
+ // should only be one ir in this array
48
+ const ir = irs[0];
47
49
  const resources = ir.getRepresentation();
48
50
  const xliff = new ResourceXliff({
49
51
  path: ir.sourceFile.getPath()
@@ -152,10 +152,10 @@ export class StringFixCommand {
152
152
 
153
153
  /**
154
154
  * Apply multiple StringFixCommands to a supplied string
155
- *
155
+ *
156
156
  * @throws when some of the provided commands overlap (as defined in {@link StringFixCommand.overlaps})
157
157
  * @throws when some of the provided commands intend to modify range outside of input string bounds
158
- *
158
+ *
159
159
  * @param {string} content string to apply commands to
160
160
  * @param {StringFixCommand[]} commands commands that should be applied to the content string
161
161
  * @return {string} modified content
@@ -59,7 +59,7 @@ class StringParser extends Parser {
59
59
 
60
60
  canWrite = true;
61
61
 
62
- /**
62
+ /**
63
63
  * @override
64
64
  * @param {IntermediateRepresentation} ir
65
65
  */
@@ -1,7 +1,7 @@
1
1
  /*
2
2
  * StringSerializer.js - Serializer for plain text files
3
3
  *
4
- * Copyright © 2024 JEDLSoft
4
+ * Copyright © 2024-2025 JEDLSoft
5
5
  *
6
6
  * Licensed under the Apache License, Version 2.0 (the "License");
7
7
  * you may not use this file except in compliance with the License.
@@ -32,7 +32,7 @@ class StringSerializer extends Serializer {
32
32
  super(options);
33
33
 
34
34
  this.name = "string";
35
- this.description = "A serializer for plain text files treats the whole file as a simple string.";
35
+ this.description = "A serializer for plain text file representations that writes the whole file as a simple string.";
36
36
  this.type = "string";
37
37
  }
38
38
 
@@ -40,11 +40,14 @@ class StringSerializer extends Serializer {
40
40
  * Convert the intermediate representation back into a source file.
41
41
  *
42
42
  * @override
43
- * @param {IntermediateRepresentation} ir the intermediate representation to convert
43
+ * @param {IntermediateRepresentation[]} irs the intermediate representations to convert
44
44
  * @returns {SourceFile} the source file with the contents of the intermediate
45
45
  * representation
46
46
  */
47
- serialize(ir) {
47
+ serialize(irs) {
48
+ // should only have 1 intermediate representation in the array because the StringParser
49
+ // only creates one
50
+ const ir = irs[0];
48
51
  const data = ir.getRepresentation();
49
52
  return new SourceFile(ir.sourceFile.getPath(), {
50
53
  file: ir.sourceFile,
@@ -2,7 +2,7 @@
2
2
  * DeclarativeResourceRule.js - subclass of ResourceRule that can iterate over
3
3
  * an arrays of regular expressions to apply to a resource
4
4
  *
5
- * Copyright © 2023 JEDLSoft
5
+ * Copyright © 2023, 2025 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.
@@ -52,7 +52,7 @@ class DeclarativeResourceRule extends ResourceRule {
52
52
  /**
53
53
  * Construct a new regular expression-based declarative resource rule.
54
54
  *
55
- * @param {Object} options options as documented above
55
+ * @param {Object} options options as documented below
56
56
  * @param {String} options.name the unique name of this rule
57
57
  * @param {String} options.description a one-line description of what
58
58
  * this rule checks for. Example: "Check that URLs in the source also
@@ -136,7 +136,7 @@ class LineRegexpChecker extends Rule {
136
136
 
137
137
  let results = [];
138
138
 
139
- // representation should be an array of lines
139
+ // representation should be an array of lines
140
140
  const lines = ir.getRepresentation();
141
141
 
142
142
  lines.forEach((line, i) => {
@@ -6,7 +6,6 @@ import {Result} from 'ilib-lint-common';
6
6
  /**
7
7
  * @classdesc Class representing an ilib-lint programmatic rule for linting camel cased strings.
8
8
  * @class
9
- * @augments ResourceRule
10
9
  */
11
10
  class ResourceCamelCase extends ResourceRule {
12
11
  /**
@@ -30,8 +29,7 @@ class ResourceCamelCase extends ResourceRule {
30
29
 
31
30
  /**
32
31
  * Check if a source string is in camel case and if the target string is the same as the source.
33
- * @public
34
- * @override ResourceRule.matchString
32
+ * @override
35
33
  * @param {{source: (String|undefined), target: (String|undefined), file: String, resource: Resource}} params
36
34
  * @returns {Result|undefined} A Result with severity 'error' if the source string is in camel case and target string is not the same as the source string, otherwise undefined.
37
35
  */
@@ -92,7 +92,7 @@ class ResourceDNTTerms extends ResourceRule {
92
92
  * @param {Object} props
93
93
  * @param {string} props.source the source string
94
94
  * @param {string} props.target the target string
95
- * @param {Resource} props.resource the resource being checked
95
+ * @param {Resource} props.resource the resource being checked
96
96
  * @param {string} props.file the file where the resource came from
97
97
  * @returns {Array.<Result>|undefined} the results
98
98
  */
@@ -117,7 +117,7 @@ class ResourceEdgeWhitespace extends ResourceRule {
117
117
  _truncateFromEnd(/** @type {string} */ str) {
118
118
  const maxLength = 5;
119
119
  const truncationMark = "…";
120
-
120
+
121
121
  if (str.length <= maxLength) {
122
122
  return str;
123
123
  }
@@ -49,7 +49,7 @@ class ResourceICUPluralTranslation extends ResourceRule {
49
49
  * "x" + "y" vs. " x" + " y "
50
50
  * Without adding the spaces and then compressing them before comparison,
51
51
  * the two would not be the same, even though the actual translatable text
52
- * in them is the same, which is what we were trying to get at.
52
+ * in them is the same, which is what we were trying to get at.
53
53
  * @private
54
54
  */
55
55
  reconstruct(nodes) {
@@ -44,7 +44,7 @@ class ResourceRule extends Rule {
44
44
  /**
45
45
  * Ensure that the rule is only applied to resources that match one of
46
46
  * the lang-specs in the the set.
47
- *
47
+ *
48
48
  * These should be language specifiers (e.g. "it", not "it-IT").
49
49
  *
50
50
  * @type {Set<string> | undefined}
@@ -55,7 +55,7 @@ class ResourceRule extends Rule {
55
55
  /**
56
56
  * Ensure that the rule is only applied to resources that do not match
57
57
  * any of the lang-specs in the set.
58
- *
58
+ *
59
59
  * These should be language specifiers (e.g. "it", not "it-IT").
60
60
  *
61
61
  * @type {Set<string> | undefined}
@@ -137,12 +137,12 @@ class ResourceRule extends Rule {
137
137
  });
138
138
  }).filter(element => element);
139
139
  return results && results.length ? results : undefined;
140
-
140
+
141
141
  case 'plural':
142
142
  const srcPlural = resource.getSource() ?? {};
143
143
  const tarPlural = resource.getTarget() ?? {};
144
144
  const categorySet = new Set(Object.keys(srcPlural).concat(Object.keys(tarPlural)));
145
-
145
+
146
146
  results = Array.from(categorySet).flatMap(category => {
147
147
  return this.matchString({
148
148
  source: srcPlural[category] ?? srcPlural.other,
@@ -6,7 +6,6 @@ import {Result} from 'ilib-lint-common';
6
6
  /**
7
7
  * @classdesc Class representing an ilib-lint programmatic rule for linting snake cased strings.
8
8
  * @class
9
- * @augments ResourceRule
10
9
  */
11
10
  class ResourceSnakeCase extends ResourceRule {
12
11
  /**
@@ -30,8 +29,7 @@ class ResourceSnakeCase extends ResourceRule {
30
29
 
31
30
  /**
32
31
  * Check if a source string is in snake case and if the target string is the same as the source.
33
- * @public
34
- * @override ResourceRule.matchString
32
+ * @override
35
33
  * @param {{source: (String|undefined), target: (String|undefined), file: String, resource: Resource}} params
36
34
  * @returns {Result|undefined} A Result with severity 'error' if the source string is in snake case and target string is not the same as the source string, otherwise undefined.
37
35
  */
@@ -2,7 +2,7 @@
2
2
  * ResourceSourceChecker.js - implement a declarative rule to check
3
3
  * source strings for problems
4
4
  *
5
- * Copyright © 2022-2024 JEDLSoft
5
+ * Copyright © 2022-2025 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.
@@ -24,7 +24,7 @@ import { stripPlurals } from './utils.js';
24
24
 
25
25
  /**
26
26
  * @class Resource checker class that checks that any regular expressions
27
- * that matches in the source also appears in the translation.
27
+ * that matches in the source causes a result to be returned.
28
28
  */
29
29
  class ResourceSourceChecker extends DeclarativeResourceRule {
30
30
  /**
@@ -2,7 +2,7 @@
2
2
  * ResourceTargetChecker.js - implement a declarative rule to check
3
3
  * target strings for problems
4
4
  *
5
- * Copyright © 2022-2024 JEDLSoft
5
+ * Copyright © 2022-2025 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.
@@ -24,7 +24,7 @@ import { stripPlurals } from './utils.js';
24
24
 
25
25
  /**
26
26
  * @class Resource checker class that checks that any regular expressions
27
- * that matches in the source also appears in the translation.
27
+ * that matches in the target causes a Result to be created.
28
28
  */
29
29
  class ResourceTargetChecker extends DeclarativeResourceRule {
30
30
  /**
@@ -1,61 +0,0 @@
1
- import {Result} from 'ilib-lint-common';
2
- import {ConfigBasedFormatter} from '../ConfigBasedFormatter.js';
3
- import ResourceMatcher from "../../rules/ResourceMatcher.js";
4
-
5
- describe('ConfigBasedFormatter', () => {
6
- it.each([
7
- {
8
- name: "a pair of opening and closing tags <eX></eX>",
9
- highlight: "This is just <e0>me</e0> testing.",
10
- expected: "This is just >>me<< testing."
11
- },
12
- {
13
- name: "a self-closing tag <eX/>",
14
- highlight: "This is just me testing.<e0/>",
15
- expected: "This is just me testing.>><<"
16
- },
17
- {
18
- name: "an opening tag <eX>",
19
- highlight: "This is just me testing.<e0>",
20
- expected: "This is just me testing.>>"
21
- },
22
- {
23
- name: "a closing tag </eX>",
24
- highlight: "This is just me testing.</e0>",
25
- expected: "This is just me testing.<<"
26
- },
27
- ])('replaces $name with highlight markers', ({highlight, expected}) => {
28
- const formatter = new ConfigBasedFormatter({
29
- "name": "test-formatter",
30
- "description": "A formatter for testing purposes",
31
- "template": "{highlight}",
32
- "highlightStart": ">>",
33
- "highlightEnd": "<<"
34
- })
35
-
36
- const result = formatter.format(new Result({
37
- description: "A description for testing purposes",
38
- highlight,
39
- id: "test.id",
40
- lineNumber: 123,
41
- pathName: "test.txt",
42
- rule: getTestRule(),
43
- severity: "error",
44
- source: "test"
45
- }));
46
-
47
-
48
- expect(result).toBe(expected);
49
- });
50
- });
51
-
52
- function getTestRule() {
53
- return new ResourceMatcher({
54
- "name": "testRule",
55
- "description": "Rule for testing purposes",
56
- "regexps": ["test"],
57
- "note": "test",
58
- "sourceLocale": "en-US",
59
- "link": ""
60
- });
61
- }