ilib-lint 2.17.0 → 2.18.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.
Files changed (38) hide show
  1. package/package.json +6 -4
  2. package/src/DirItem.js +40 -21
  3. package/src/FileType.js +91 -72
  4. package/src/LintableFile.js +44 -128
  5. package/src/LintingStrategy.js +197 -0
  6. package/src/ParserManager.js +19 -30
  7. package/src/Project.js +272 -186
  8. package/src/RuleManager.js +56 -32
  9. package/src/RuleSet.js +11 -10
  10. package/src/plugins/BuiltinPlugin.js +17 -2
  11. package/src/plugins/LineSerializer.js +10 -0
  12. package/src/plugins/XliffSerializer.js +35 -1
  13. package/src/plugins/byte/ByteFix.js +55 -0
  14. package/src/plugins/byte/ByteFixer.js +65 -0
  15. package/src/plugins/byte/ByteParser.js +62 -0
  16. package/src/plugins/byte/ByteSerializer.js +59 -0
  17. package/src/plugins/positional/PositionalFixCommand.js +192 -0
  18. package/src/plugins/string/StringFixCommand.js +32 -99
  19. package/src/plugins/string/StringFixer.js +11 -5
  20. package/src/plugins/string/StringSerializer.js +10 -0
  21. package/src/rules/ResourceCamelCase.js +3 -2
  22. package/src/rules/ResourceCompleteness.js +2 -1
  23. package/src/rules/ResourceEdgeWhitespace.js +2 -1
  24. package/src/rules/ResourceGNUPrintfMatch.js +2 -0
  25. package/src/rules/ResourceICUPlurals.js +24 -17
  26. package/src/rules/ResourceKebabCase.js +3 -2
  27. package/src/rules/ResourceNoTranslation.js +2 -2
  28. package/src/rules/ResourceQuoteStyle.js +3 -4
  29. package/src/rules/ResourceSentenceEnding.js +303 -196
  30. package/src/rules/ResourceSnakeCase.js +3 -2
  31. package/src/rules/ResourceSourceICUPluralCategories.js +2 -1
  32. package/src/rules/ResourceSourceICUPluralParams.js +2 -1
  33. package/src/rules/ResourceSourceICUUnexplainedParams.js +3 -2
  34. package/src/rules/ResourceUniqueKeys.js +17 -18
  35. package/src/rules/ResourceXML.js +9 -8
  36. package/src/rules/byte/BOMRule.js +101 -0
  37. package/src/rules/byte/FileEncodingRule.js +113 -0
  38. package/src/rules/string/XliffHeaderEncoding.js +121 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ilib-lint",
3
- "version": "2.17.0",
3
+ "version": "2.18.0",
4
4
  "type": "module",
5
5
  "main": "./src/index.js",
6
6
  "module": "./src/index.js",
@@ -51,10 +51,12 @@
51
51
  "devDependencies": {
52
52
  "@tsconfig/node14": "^14.1.2",
53
53
  "@types/jest": "^29.5.14",
54
+ "@types/micromatch": "^4.0.9",
54
55
  "@types/node": "^14.0.0",
55
56
  "dedent": "^1.5.3",
56
57
  "docdash": "^2.0.2",
57
58
  "i18nlint-plugin-test-old": "file:test/i18nlint-plugin-test-old",
59
+ "ilib-es6": "^14.21.0",
58
60
  "ilib-lint-plugin-obsolete": "file:test/ilib-lint-plugin-obsolete",
59
61
  "ilib-lint-plugin-test": "file:test/ilib-lint-plugin-test",
60
62
  "jest": "^29.7.0",
@@ -73,10 +75,10 @@
73
75
  "options-parser": "^0.4.0",
74
76
  "xml-js": "^1.6.11",
75
77
  "ilib-common": "^1.1.6",
76
- "ilib-locale": "^1.2.4",
77
- "ilib-lint-common": "^3.4.0",
78
78
  "ilib-ctype": "^1.2.2",
79
- "ilib-tools-common": "^1.19.0"
79
+ "ilib-lint-common": "^3.6.0",
80
+ "ilib-tools-common": "^1.19.0",
81
+ "ilib-locale": "^1.2.4"
80
82
  },
81
83
  "scripts": {
82
84
  "coverage": "pnpm test -- --coverage",
package/src/DirItem.js CHANGED
@@ -17,9 +17,10 @@
17
17
  * limitations under the License.
18
18
  */
19
19
 
20
- import log4js from 'log4js';
20
+ import { Result } from "ilib-lint-common";
21
21
 
22
- const logger = log4js.getLogger("ilib-lint.DirItem");
22
+ // type imports
23
+ /** @ignore @typedef {import("./Project.js").default} Project */
23
24
 
24
25
  /**
25
26
  * @class Represent a directory item.
@@ -30,15 +31,33 @@ const logger = log4js.getLogger("ilib-lint.DirItem");
30
31
  * @abstract
31
32
  */
32
33
  class DirItem {
34
+ /**
35
+ * The file path for this directory item
36
+ * @type {String}
37
+ */
38
+ filePath;
39
+
40
+ /**
41
+ * The settings from the ilib-lint config that apply to this file
42
+ * @type {Record<string, unknown> | undefined}
43
+ */
44
+ settings;
45
+
46
+ /**
47
+ * The project that this directory item is part of
48
+ * @type {Project|undefined}
49
+ */
50
+ project;
51
+
33
52
  /**
34
53
  * Construct a new directory item
35
54
  * The options parameter can contain any of the following properties:
36
55
  *
37
- * - filePath {String} path to the file
38
- * - settings {Object} the settings from the ilib-lint config that
56
+ * @param {String} filePath path to the file
57
+ * @param {Object} options options for constructing this directory item
58
+ * @param {Record<string, unknown>} [options.settings] the settings from the ilib-lint config that
39
59
  * apply to this file
40
- * - pluginManager {PluginManager} the plugin manager for this run of
41
- * the ilib-lint tool
60
+ * @param {Project} [project] the project that this directory item is part of
42
61
  */
43
62
  constructor(filePath, options, project) {
44
63
  if (!options || !filePath) {
@@ -46,36 +65,36 @@ class DirItem {
46
65
  }
47
66
  this.filePath = filePath;
48
67
  this.settings = options.settings;
49
- this.pluginMgr = options.pluginManager;
50
68
  this.project = project;
51
69
  }
52
70
 
53
71
  /**
54
- * Return the file path for this source file.
55
- *
56
- * @returns {String} the file path for this source file
72
+ * Initialize this directory item.
73
+ * @returns {Promise<void>} a promise to initialize the directory item
57
74
  */
58
- getFilePath() {
59
- return this.filePath;
75
+ async init() {
76
+ return Promise.resolve();
60
77
  }
61
78
 
62
79
  /**
63
- * Parse the current directory item.
80
+ * Return the file path for this directory item.
64
81
  *
65
- * @returns {Array.<IntermediateRepresentation>} the parsed
66
- * representations of this file
67
- * @abstract
82
+ * @returns {String} the file path for this directory item
68
83
  */
69
- parse() {}
84
+ getFilePath() {
85
+ return this.filePath;
86
+ }
70
87
 
71
88
  /**
72
89
  * Check the directory item and return a list of issues found in it.
73
90
  *
74
- * @param {Array.<Locale>} locales a set of locales to apply
75
- * @returns {Array.<Result>} a list of natch results
91
+ * @param {Array.<string>} locales a set of locales to apply
92
+ * @returns {Array.<Result>} a list of match results
76
93
  * @abstract
77
94
  */
78
- findIssues(locales) {}
79
- };
95
+ findIssues(locales) {
96
+ throw new Error("Not implemented");
97
+ }
98
+ }
80
99
 
81
100
  export default DirItem;
package/src/FileType.js CHANGED
@@ -17,9 +17,14 @@
17
17
  * limitations under the License.
18
18
  */
19
19
 
20
- import log4js from 'log4js';
20
+ import log4js from "log4js";
21
21
 
22
- import RuleSet from './RuleSet.js';
22
+ import RuleSet from "./RuleSet.js";
23
+ import { Fixer, Parser, Rule, Serializer, Transformer } from "ilib-lint-common";
24
+ import Project from "./Project.js";
25
+
26
+ // type imports
27
+ /** @ignore @typedef {import("./RuleManager.js").RuleSetDefinition} RuleSetDefinition */
23
28
 
24
29
  const logger = log4js.getLogger("ilib-lint.FileType");
25
30
 
@@ -35,74 +40,65 @@ class FileType {
35
40
  /**
36
41
  * The lint project that this file is a part of.
37
42
  * @type {Project}
43
+ * @readonly
38
44
  */
39
45
  project;
40
46
 
41
47
  /**
42
48
  * The name or glob spec for this file type
43
49
  * @type {String|undefined}
50
+ * @readonly
44
51
  */
45
52
  name;
46
53
 
47
54
  /**
48
55
  * The list of locales to use with this file type
49
56
  * @type {Array.<String>|undefined}
57
+ * @readonly
50
58
  */
51
59
  locales;
52
60
 
53
61
  /**
54
62
  * The intermediate representation type of this file type.
55
63
  * @type {String}
64
+ * @readonly
56
65
  */
57
66
  type;
58
67
 
59
- /**
60
- * The array of names of classes of parsers to use with this file type.
61
- * @type {Array.<String>|undefined}
62
- */
63
- parsers = undefined;
64
-
65
68
  /**
66
69
  * The array of classes of parsers to use with this file type.
67
- * @type {Array.<Class>|undefined}
70
+ * @type {Array.<Parser>|undefined}
71
+ * @readonly
68
72
  */
69
- parserClasses = undefined;
70
-
71
- /**
72
- * The array of names of transformers to use with this file type.
73
- * @type {Array.<String>|undefined}
74
- */
75
- transformers = undefined;
73
+ parsers;
76
74
 
77
75
  /**
78
76
  * The array of instances of transformers to use with this file type.
79
77
  * @type {Array.<Transformer>|undefined}
78
+ * @readonly
80
79
  */
81
- transformerInstances = undefined;
82
-
83
- /**
84
- * The serializer to use with this file type.
85
- * @type {String|undefined}
86
- */
87
- serializer = undefined;
80
+ transformers;
88
81
 
89
82
  /**
90
83
  * The instance of the serializer to use with this file type.
91
84
  * @type {Serializer|undefined}
85
+ * @readonly
92
86
  */
93
- serializerInst = undefined;
87
+ serializer;
94
88
 
95
89
  /**
96
90
  * The array of rule sets to apply to files of this type.
97
91
  * @type {Array<String>|undefined}
92
+ * @readonly
98
93
  */
99
- ruleset = undefined;
94
+ ruleset;
100
95
 
101
96
  /**
102
97
  * The path template for this file type.
103
98
  * @type {String|undefined}
99
+ * @readonly
104
100
  */
105
- template = undefined;
101
+ template;
106
102
 
107
103
  /**
108
104
  * Contructor a new instance of a file type.
@@ -131,14 +127,14 @@ class FileType {
131
127
  * type of the rules, and the type of the transformers and serializer. If no
132
128
  * parsers are specified, then the parser manager will be asked to find all
133
129
  * parsers that can parse files of this type.
134
- * @param {Array.<String>} [options.ruleset] a list of rule set names
130
+ * @param {Array.<String>|String|RuleSetDefinition} [options.ruleset] a list of rule set names
135
131
  * to use with this file type. Only rules in these rule sets that operate
136
132
  * on the same type of intermediate representation as the parsers will
137
133
  * be applied to the file.
138
134
  * @param {Array.<String>} [options.transformers] an array of transformer names
139
135
  * to apply to files of this type after the rules have been applied. Every transformer
140
136
  * must operate on the same type of intermediate representation as the parser.
141
- * @param {String|Object} [options.serializer] the name of the serializer to use if
137
+ * @param {String} [options.serializer] the name of the serializer to use if
142
138
  * the file has been modified by a transformer or a fixer. The serializer must
143
139
  * operate on the same type of intermediate representation as the parser.
144
140
  * @constructor
@@ -150,80 +146,93 @@ class FileType {
150
146
  if (!options || !options.name || !options.project) {
151
147
  throw "Missing required options to the FileType constructor";
152
148
  }
153
- ["name", "project", "locales", "ruleset", "template", "parsers", "transformers", "serializer"].forEach(prop => {
154
- if (typeof(options[prop]) !== 'undefined') {
155
- this[prop] = options[prop];
156
- }
157
- });
158
149
 
159
- if (this.parsers) {
150
+ /** @type {String|undefined} */
151
+ let inferredType = undefined;
152
+
153
+ this.name = options.name;
154
+ this.project = options.project;
155
+ this.locales = options.locales;
156
+ this.template = options.template;
157
+
158
+ const parserNames = options.parsers;
159
+ if (parserNames) {
160
160
  const parserMgr = this.project.getParserManager();
161
- this.parserClasses = this.parsers.map(parserName => {
161
+ this.parsers = parserNames.map((parserName) => {
162
162
  const parser = parserMgr.getByName(parserName);
163
163
  if (!parser) {
164
164
  throw `Could not find parser ${parserName} named in the configuration for filetype ${this.name}`;
165
165
  }
166
- if (!this.type) {
167
- this.type = parserMgr.getType(parserName);
166
+ if (!inferredType) {
167
+ inferredType = parserMgr.getType(parserName);
168
168
  }
169
169
  return parser;
170
170
  });
171
171
  }
172
172
 
173
- if (this.ruleset) {
174
- if (typeof(this.ruleset) === 'string') {
173
+ const unknownRuleset = options.ruleset;
174
+ if (unknownRuleset) {
175
+ if (Array.isArray(unknownRuleset)) {
176
+ this.ruleset = unknownRuleset;
177
+ } else if (typeof unknownRuleset === "string") {
175
178
  // single string -> convert to an array with a single element
176
- this.ruleset = [ this.ruleset ];
177
- } else if (!Array.isArray(this.ruleset)) {
179
+ this.ruleset = [unknownRuleset];
180
+ } else {
178
181
  // rule set definition instead of a ruleset name. Save a new
179
182
  // rule set definition in the rule manager and give it a
180
183
  // temp name so we can refer to it and make sure that this.ruleset
181
184
  // always points to an array of rule set names
182
185
  const ruleMgr = this.project.getRuleManager();
183
186
  const setName = `${this.name}-unnamed-ruleset`;
184
- ruleMgr.addRuleSetDefinition(setName, this.ruleset);
185
- this.ruleset = [ setName ];
187
+ ruleMgr.addRuleSetDefinition(setName, unknownRuleset);
188
+ this.ruleset = [setName];
186
189
  }
187
190
  }
188
191
 
189
- if (this.transformers) {
190
- const names = Array.isArray(this.transformers) ? this.transformers : [ this.transformers ];
192
+ const transformerNames = options.transformers;
193
+ if (transformerNames) {
194
+ const names = Array.isArray(transformerNames) ? transformerNames : [transformerNames];
191
195
  const transformerMgr = this.project.getTransformerManager();
192
- this.transformerInstances = names.map(transformerName => {
196
+ this.transformers = names.map((transformerName) => {
193
197
  const transformer = transformerMgr.get(transformerName);
194
198
  if (!transformer) {
195
199
  throw `Could not find transformer ${transformerName} named in the configuration for filetype ${this.name}`;
196
200
  }
197
201
  const transformerType = transformer.getType();
198
- if (!this.type) {
199
- this.type = transformerType;
200
- } else if (transformerType !== this.type) {
201
- throw new Error(`The transformer ${transformerName} processes representations of type ${transformerType}, but the filetype ${this.name} handles representations of type ${this.type}. The two types must match.`);
202
+ if (!inferredType) {
203
+ inferredType = transformerType;
204
+ } else if (transformerType !== inferredType) {
205
+ throw new Error(
206
+ `The transformer ${transformerName} processes representations of type ${transformerType}, but the filetype ${this.name} handles representations of type ${inferredType}. The two types must match.`
207
+ );
202
208
  }
203
209
  return transformer;
204
210
  });
205
211
  }
206
212
 
207
- if (this.serializer) {
208
- // if it is a string, then that string is the name of the serializer. If it is an object,
209
- // then it the name and the settings to pass to the the serializer constructor.
210
- const name = typeof(this.serializer) === 'string' ? this.serializer : this.serializer.name;
213
+ const serializerName = options.serializer;
214
+ if (serializerName) {
211
215
  const serializerMgr = this.project.getSerializerManager();
212
- this.serializerInst = serializerMgr.get(name, this.serializer);
213
- if (!this.serializerInst) {
214
- throw new Error(`Could not find or instantiate serializer ${this.serializer} named in the configuration for filetype ${this.name}`);
216
+ this.serializer = serializerMgr.get(serializerName);
217
+ if (!this.serializer) {
218
+ throw new Error(
219
+ `Could not find or instantiate serializer ${serializerName} named in the configuration for filetype ${this.name}`
220
+ );
215
221
  }
216
- const serializerType = this.serializerInst.getType();
217
- if (!this.type) {
218
- this.type = serializerType;
219
- } else if (serializerType !== this.type) {
220
- throw new Error(`The serializer ${name} processes representations of type ${serializerType}, but the filetype ${this.name} handles representations of type ${this.type}. The two types must match.`);
222
+ const serializerType = this.serializer.getType();
223
+ if (!inferredType) {
224
+ inferredType = serializerType;
225
+ } else if (serializerType !== inferredType) {
226
+ throw new Error(
227
+ `The serializer ${serializerName} processes representations of type ${serializerType}, but the filetype ${this.name} handles representations of type ${inferredType}. The two types must match.`
228
+ );
221
229
  }
222
230
  }
223
231
 
224
- if (!this.type) {
225
- this.type = "string";
232
+ if (!inferredType) {
233
+ inferredType = "string";
226
234
  }
235
+ this.type = inferredType;
227
236
  }
228
237
 
229
238
  getName() {
@@ -253,11 +262,11 @@ class FileType {
253
262
  * that can parse files with the given file name extension. If there
254
263
  * are none available, this method returned undefined;
255
264
  * @param {String} extension file name extension of the file being parsed
256
- * @returns {Array.<Class>} an array of parser classes to use with
265
+ * @returns {Parser[]} an array of parser classes to use with
257
266
  * files of this type.
258
267
  */
259
- getParserClasses(extension) {
260
- if (this.parserClasses) return this.parserClasses;
268
+ getParsers(extension) {
269
+ if (this.parsers) return this.parsers;
261
270
  const pm = this.project.getParserManager();
262
271
  return pm.get(extension);
263
272
  }
@@ -268,7 +277,7 @@ class FileType {
268
277
  * @returns {Array.<String>} a list of rule set names
269
278
  */
270
279
  getRuleSetNames() {
271
- return this.ruleset;
280
+ return this.ruleset || [];
272
281
  }
273
282
 
274
283
  /**
@@ -284,14 +293,14 @@ class FileType {
284
293
 
285
294
  const ruleMgr = this.project.getRuleManager();
286
295
  const set = new RuleSet();
287
- this.ruleset.forEach(ruleSetName => {
296
+ this.ruleset.forEach((ruleSetName) => {
288
297
  const definitions = ruleMgr.getRuleSetDefinition(ruleSetName);
289
298
  if (!definitions) {
290
299
  logger.error(`Could not find rule set ${ruleSetName}`);
291
300
  return;
292
301
  }
293
302
  for (let ruleName in definitions) {
294
- if (typeof(definitions[ruleName]) === 'boolean') {
303
+ if (typeof definitions[ruleName] === "boolean") {
295
304
  if (definitions[ruleName]) {
296
305
  set.addRule(ruleMgr.get(ruleName));
297
306
  } else {
@@ -317,7 +326,7 @@ class FileType {
317
326
  * with this file type, or undefined if there are none.
318
327
  */
319
328
  getTransformers() {
320
- return this.transformerInstances;
329
+ return this.transformers;
321
330
  }
322
331
 
323
332
  /**
@@ -327,7 +336,17 @@ class FileType {
327
336
  * file type or undefined if there is no serializer for this file type.
328
337
  */
329
338
  getSerializer() {
330
- return this.serializerInst;
339
+ return this.serializer;
340
+ }
341
+
342
+ /**
343
+ * Return an instance of the fixer class for this file type.
344
+ *
345
+ * @returns {Fixer|undefined} an instance of the fixer class for this
346
+ * file type or undefined if there is no fixer for this file type.
347
+ */
348
+ getFixer() {
349
+ return this.project.getFixerManager().get(this.type);
331
350
  }
332
351
  }
333
352