ilib-lint 1.0.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 (89) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +231 -0
  3. package/docs/AnsiConsoleFormatter.html +467 -0
  4. package/docs/Formatter.html +577 -0
  5. package/docs/Formatter.js.html +135 -0
  6. package/docs/FormatterFactory.html +191 -0
  7. package/docs/FormatterFactory.js.html +109 -0
  8. package/docs/Parser.html +483 -0
  9. package/docs/Parser.js.html +122 -0
  10. package/docs/ParserFactory.js.html +109 -0
  11. package/docs/Plugin.html +847 -0
  12. package/docs/Plugin.js.html +168 -0
  13. package/docs/PluginManager.html +541 -0
  14. package/docs/PluginManager.js.html +125 -0
  15. package/docs/ResourceICUPlurals.html +278 -0
  16. package/docs/ResourceQuoteStyle.html +278 -0
  17. package/docs/ResourceRegExpChecker.html +295 -0
  18. package/docs/ResourceUniqueKeys.html +278 -0
  19. package/docs/Result.html +263 -0
  20. package/docs/Result.js.html +130 -0
  21. package/docs/Rule.html +774 -0
  22. package/docs/Rule.js.html +230 -0
  23. package/docs/RuleSet.html +760 -0
  24. package/docs/RuleSet.js.html +153 -0
  25. package/docs/SourceFile.html +826 -0
  26. package/docs/SourceFile.js.html +232 -0
  27. package/docs/XliffParser.html +396 -0
  28. package/docs/XliffPlugin.html +472 -0
  29. package/docs/fonts/Montserrat/Montserrat-Bold.eot +0 -0
  30. package/docs/fonts/Montserrat/Montserrat-Bold.ttf +0 -0
  31. package/docs/fonts/Montserrat/Montserrat-Bold.woff +0 -0
  32. package/docs/fonts/Montserrat/Montserrat-Bold.woff2 +0 -0
  33. package/docs/fonts/Montserrat/Montserrat-Regular.eot +0 -0
  34. package/docs/fonts/Montserrat/Montserrat-Regular.ttf +0 -0
  35. package/docs/fonts/Montserrat/Montserrat-Regular.woff +0 -0
  36. package/docs/fonts/Montserrat/Montserrat-Regular.woff2 +0 -0
  37. package/docs/fonts/Source-Sans-Pro/sourcesanspro-light-webfont.eot +0 -0
  38. package/docs/fonts/Source-Sans-Pro/sourcesanspro-light-webfont.svg +978 -0
  39. package/docs/fonts/Source-Sans-Pro/sourcesanspro-light-webfont.ttf +0 -0
  40. package/docs/fonts/Source-Sans-Pro/sourcesanspro-light-webfont.woff +0 -0
  41. package/docs/fonts/Source-Sans-Pro/sourcesanspro-light-webfont.woff2 +0 -0
  42. package/docs/fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.eot +0 -0
  43. package/docs/fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.svg +1049 -0
  44. package/docs/fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.ttf +0 -0
  45. package/docs/fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.woff +0 -0
  46. package/docs/fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.woff2 +0 -0
  47. package/docs/formatters_AnsiConsoleFormatter.js.html +147 -0
  48. package/docs/global.html +448 -0
  49. package/docs/ilibLint.md +1013 -0
  50. package/docs/index.html +81 -0
  51. package/docs/plugins_XliffParser.js.html +129 -0
  52. package/docs/plugins_XliffPlugin.js.html +129 -0
  53. package/docs/rules_ResourceICUPlurals.js.html +297 -0
  54. package/docs/rules_ResourceQuoteStyle.js.html +238 -0
  55. package/docs/rules_ResourceRegExpChecker.js.html +248 -0
  56. package/docs/rules_ResourceUniqueKeys.js.html +144 -0
  57. package/docs/scripts/collapse.js +20 -0
  58. package/docs/scripts/linenumber.js +25 -0
  59. package/docs/scripts/nav.js +12 -0
  60. package/docs/scripts/polyfill.js +4 -0
  61. package/docs/scripts/prettify/Apache-License-2.0.txt +202 -0
  62. package/docs/scripts/prettify/lang-css.js +2 -0
  63. package/docs/scripts/prettify/prettify.js +28 -0
  64. package/docs/scripts/search.js +83 -0
  65. package/docs/styles/jsdoc.css +765 -0
  66. package/docs/styles/prettify.css +79 -0
  67. package/docs/walk.js.html +214 -0
  68. package/log4js.json +21 -0
  69. package/package.json +83 -0
  70. package/src/Formatter.js +66 -0
  71. package/src/FormatterFactory.js +41 -0
  72. package/src/Parser.js +53 -0
  73. package/src/ParserFactory.js +41 -0
  74. package/src/Plugin.js +99 -0
  75. package/src/PluginManager.js +56 -0
  76. package/src/Result.js +62 -0
  77. package/src/Rule.js +162 -0
  78. package/src/RuleSet.js +84 -0
  79. package/src/SourceFile.js +163 -0
  80. package/src/formatters/AnsiConsoleFormatter.js +78 -0
  81. package/src/index.js +213 -0
  82. package/src/plugins/XliffParser.js +60 -0
  83. package/src/plugins/XliffPlugin.js +60 -0
  84. package/src/rules/ResourceICUPlurals.js +229 -0
  85. package/src/rules/ResourceQuoteStyle.js +170 -0
  86. package/src/rules/ResourceRegExpChecker.js +179 -0
  87. package/src/rules/ResourceUniqueKeys.js +76 -0
  88. package/src/rules/utils.js +78 -0
  89. package/src/walk.js +146 -0
@@ -0,0 +1,170 @@
1
+ /*
2
+ * ResourceQuoteStyle.js - rule to check quotes in the target string
3
+ *
4
+ * Copyright © 2022 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 LocaleInfo from 'ilib-localeinfo';
21
+
22
+ import Rule from '../Rule.js';
23
+ import Result from '../Result.js';
24
+
25
+ let LICache = {};
26
+
27
+ // superset of all the start and end chars used in CLDR
28
+ const quoteChars = "«»‘“”„「」’‚‹›『』\"\'";
29
+
30
+ /**
31
+ * @class Represent an ilib-lint rule.
32
+ */
33
+ class ResourceQuoteStyle extends Rule {
34
+ constructor(options) {
35
+ super(options);
36
+ this.name = "resource-quote-style";
37
+ this.description = "Ensure that the proper quote characters are used in translated resources";
38
+ this.sourceLocale = (options && options.sourceLocale) || "en-US";
39
+ }
40
+
41
+ getRuleType() {
42
+ return "resource";
43
+ }
44
+
45
+ /**
46
+ * @override
47
+ */
48
+ match(options) {
49
+ const { locale, resource, file } = options || {};
50
+ let li = LICache[locale];
51
+ const _this = this;
52
+
53
+ if (!li) {
54
+ li = new LocaleInfo(locale);
55
+ LICache[locale] = li;
56
+ }
57
+
58
+ let sourceLI = LICache[this.sourceLocale];
59
+ if (!sourceLI) {
60
+ sourceLI = new LocaleInfo(this.sourceLocale);
61
+ LICache[this.sourceLocale] = sourceLI;
62
+ }
63
+
64
+ const srcQuoteStart = sourceLI.getDelimiterQuotationStart();
65
+ const srcAltQuoteStart = sourceLI.info.delimiter.alternateQuotationStart;
66
+
67
+ const srcQuoteEnd = sourceLI.getDelimiterQuotationEnd();
68
+ const srcAltQuoteEnd = sourceLI.info.delimiter.alternateQuotationEnd;
69
+
70
+ const tarQuoteStart = li.getDelimiterQuotationStart();
71
+ const tarAltQuoteStart = li.info.delimiter.alternateQuotationStart;
72
+
73
+ const tarQuoteEnd = li.getDelimiterQuotationEnd();
74
+ const tarAltQuoteEnd = li.info.delimiter.alternateQuotationEnd;
75
+
76
+ // if the source uses ASCII quotes, then the target could have ASCII or native quotes
77
+ const srcQuotesAscii = new RegExp(`((^|\\W)['"]\\p{Letter}|\\p{Letter}['"](\\W|$))`, "gu");
78
+ const srcQuotesNative = new RegExp(`((^|\\W)[${srcQuoteStart}${srcAltQuoteStart}]\\p{Letter}|\\p{Letter}[${srcQuoteEnd}${srcAltQuoteEnd}](\\W|$))`, "gu");
79
+
80
+ // if the source contains native quotes, then the target should also have native quotes
81
+ const tarQuotesAll = new RegExp(`((^|\\W)[${tarQuoteStart}${tarAltQuoteStart}'"]\\p{Letter}|\\p{Letter}[${tarQuoteEnd}${tarAltQuoteEnd}'"](\\W|$))`, "gu");
82
+ const tarQuotesNative = new RegExp(`((^|\\W)[${tarQuoteStart}${tarAltQuoteStart}]\\p{Letter}|\\p{Letter}[${tarQuoteEnd}${tarAltQuoteEnd}](\\W|$))`, "gu");
83
+
84
+ const nonQuoteChars = `([${
85
+ quoteChars.
86
+ replace(srcQuoteStart, "").
87
+ replace(srcAltQuoteStart, "").
88
+ replace(tarQuoteStart, "").
89
+ replace(tarAltQuoteStart, "").
90
+ replace(srcQuoteEnd, "").
91
+ replace(srcAltQuoteEnd, "").
92
+ replace(tarQuoteEnd, "").
93
+ replace(tarAltQuoteEnd, "")}])`;
94
+ const re = new RegExp(nonQuoteChars, "g");
95
+
96
+ /**
97
+ * @private
98
+ */
99
+ function checkString(src, tar) {
100
+ srcQuotesAscii.lastIndex = 0;
101
+ tarQuotesAll.lastIndex = 0;
102
+ if ((src.match(srcQuotesAscii) && !tar.match(tarQuotesAll)) ||
103
+ (src.match(srcQuotesNative) && !tar.match(tarQuotesNative))) {
104
+ const matches = re.exec(tar);
105
+ let value = {
106
+ severity: "warning",
107
+ id: resource.getKey(),
108
+ source: src,
109
+ rule: _this,
110
+ pathName: file
111
+ };
112
+ if (matches) {
113
+ value.highlight = `Target: ${tar.replace(re, "<e0>$1</e0>")}`;
114
+ value.description = `Quote style for the the locale ${locale} should be ${tarQuoteStart}text${tarQuoteEnd}`;
115
+ } else {
116
+ value.highlight = `Target: ${tar}<e0></e0>`;
117
+ value.description = `Quotes are missing in the target. Quote style for the the locale ${locale} should be ${tarQuoteStart}text${tarQuoteEnd}`;
118
+ }
119
+ if (typeof(options.lineNumber) !== 'undefined') {
120
+ value.lineNumber = options.lineNumber;
121
+ }
122
+ return new Result(value);
123
+ }
124
+ }
125
+
126
+ switch (resource.getType()) {
127
+ case 'string':
128
+ const tarString = resource.getTarget();
129
+ if (tarString) {
130
+ return checkString(resource.getSource(), tarString);
131
+ }
132
+ break;
133
+
134
+ case 'array':
135
+ const srcArray = resource.getSource();
136
+ const tarArray = resource.getTarget();
137
+ if (tarArray) {
138
+ return srcArray.map((item, i) => {
139
+ if (i < tarArray.length && tarArray[i]) {
140
+ return checkString(srcArray[i], tarArray[i]);
141
+ }
142
+ }).filter(element => {
143
+ return element;
144
+ });
145
+ }
146
+ break;
147
+
148
+ case 'plural':
149
+ const srcPlural = resource.getSource();
150
+ const tarPlural = resource.getTarget();
151
+ if (tarPlural) {
152
+ const hasQuotes = categories.find(category => {
153
+ return (srcPlural[category] && srcPlural[category].contains(srcQuote));
154
+ });
155
+
156
+ if (hasQuotes) {
157
+ return categories.map(category => {
158
+ return checkString(srcPlural.other, tarPlural[category]);
159
+ });
160
+ }
161
+ }
162
+ break;
163
+ }
164
+ }
165
+
166
+ // no match
167
+ return;
168
+ }
169
+
170
+ export default ResourceQuoteStyle;
@@ -0,0 +1,179 @@
1
+ /*
2
+ * ResourceRegExpChecker.js - rule to check if URLs in the source string also
3
+ * appear in the target string
4
+ *
5
+ * Copyright © 2022 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 Rule from '../Rule.js';
22
+ import Result from '../Result.js';
23
+ import { stripPlurals } from './utils.js';
24
+
25
+ function findMissing(source, target) {
26
+ let missing = [];
27
+ for (var i = 0; i < source.length; i++) {
28
+ if (target.indexOf(source[i]) < 0) {
29
+ missing.push(source[i]);
30
+ }
31
+ }
32
+ return missing;
33
+ }
34
+
35
+ /**
36
+ * @class Resource checker class that checks that any regular expressions
37
+ * that matches in the source also appears in the translation.
38
+ */
39
+ class ResourceRegExpChecker extends Rule {
40
+ /**
41
+ * Construct a new regular expression-based resource checker.
42
+ *
43
+ * The options must contain the following required properties:
44
+ *
45
+ * - name - a unique name for this rule
46
+ * - description - a one-line description of what this rule checks for.
47
+ * Example: "Check that URLs in the source also appear in the target"
48
+ * - note - a one-line note that will be printed on screen when the
49
+ * check fails. Example: "The URL {matchString} did not appear in the
50
+ * the target." (Currently, matchString is the only replacement
51
+ * param that is supported.)
52
+ * - regexps - an array of strings that encode regular expressions to
53
+ * look for
54
+ */
55
+ constructor(options) {
56
+ super(options);
57
+
58
+ if (!options || !options.name || !options.description || !options.note || !options.regexps) {
59
+ throw "Missing required options for the ResourceRegExpChecker constructor";
60
+ }
61
+ ["name", "description", "regexps", "note", "sourceLocale"].forEach(prop => {
62
+ this[prop] = options[prop];
63
+ });
64
+ this.sourceLocale = this.sourceLocale || "en-US";
65
+
66
+ // this may throw if you got to the syntax wrong:
67
+ this.re = this.regexps.map(regexp => new RegExp(regexp, "g"));
68
+ }
69
+
70
+ getRuleType() {
71
+ return "resource";
72
+ }
73
+
74
+ /**
75
+ * @override
76
+ */
77
+ match(options) {
78
+ const { locale, resource, file } = options || {};
79
+ const _this = this;
80
+
81
+ /**
82
+ * @private
83
+ */
84
+ function checkString(re, src, tar) {
85
+ re.lastIndex = 0;
86
+ let sourceMatches = [];
87
+ const strippedSrc = stripPlurals(src);
88
+ const strippedTar = stripPlurals(tar);
89
+
90
+ let match = re.exec(strippedSrc);
91
+ while (match) {
92
+ sourceMatches.push(match[0]);
93
+ match = re.exec(strippedSrc);
94
+ }
95
+
96
+ if (sourceMatches.length > 0) {
97
+ // contains URLs, so check the target
98
+ re.lastIndex = 0;
99
+ let targetMatches = [];
100
+ match = re.exec(strippedTar);
101
+ while (match) {
102
+ targetMatches.push(match[0]);
103
+ match = re.exec(strippedTar);
104
+ }
105
+ const missing = findMissing(sourceMatches, targetMatches);
106
+ if (missing.length > 0) {
107
+ return missing.map(missing => {
108
+ let value = {
109
+ severity: "error",
110
+ id: resource.getKey(),
111
+ source: src,
112
+ rule: _this,
113
+ pathName: file,
114
+ highlight:`Target: ${tar}<e0></e0>`,
115
+ description: _this.note.replace(/\{matchString\}/g, missing)
116
+ };
117
+ if (typeof(options.lineNumber) !== 'undefined') {
118
+ value.lineNumber = options.lineNumber;
119
+ }
120
+ return new Result(value);
121
+ });
122
+ }
123
+ }
124
+ }
125
+
126
+ function checkRegExps(src, tar) {
127
+ let results = [];
128
+ _this.re.forEach(re => {
129
+ results = results.concat(checkString(re, src, tar));
130
+ });
131
+ results = results.filter(result => result);
132
+ return results && results.length ? results : undefined;
133
+ }
134
+
135
+ switch (resource.getType()) {
136
+ case 'string':
137
+ const tarString = resource.getTarget();
138
+ if (tarString) {
139
+ return checkRegExps(resource.getSource(), tarString);
140
+ }
141
+ break;
142
+
143
+ case 'array':
144
+ const srcArray = resource.getSource();
145
+ const tarArray = resource.getTarget();
146
+ if (tarArray) {
147
+ return srcArray.map((item, i) => {
148
+ if (i < tarArray.length && tarArray[i]) {
149
+ return checkRegExps(srcArray[i], tarArray[i]);
150
+ }
151
+ }).filter(element => {
152
+ return element;
153
+ });
154
+ }
155
+ break;
156
+
157
+ case 'plural':
158
+ const srcPlural = resource.getSource();
159
+ const tarPlural = resource.getTarget();
160
+ if (tarPlural) {
161
+ const hasQuotes = categories.find(category => {
162
+ return (srcPlural[category] && srcPlural[category].contains(srcQuote));
163
+ });
164
+
165
+ if (hasQuotes) {
166
+ return categories.map(category => {
167
+ return checkRegExps(srcPlural.other, tarPlural[category]);
168
+ });
169
+ }
170
+ }
171
+ break;
172
+ }
173
+ }
174
+
175
+ // no match
176
+ return;
177
+ }
178
+
179
+ export default ResourceRegExpChecker;
@@ -0,0 +1,76 @@
1
+ /*
2
+ * ResourceUniqueKeys.js - rule to check quotes in the target string
3
+ *
4
+ * Copyright © 2022 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 LocaleInfo from 'ilib-localeinfo';
21
+ import { TranslationSet } from 'ilib-tools-common';
22
+
23
+ import Rule from '../Rule.js';
24
+ import Result from '../Result.js';
25
+
26
+ /**
27
+ * @class Represent an ilib-lint rule.
28
+ */
29
+ class ResourceUniqueKeys extends Rule {
30
+ constructor(options) {
31
+ super(options);
32
+ this.name = "resource-unique-keys";
33
+ this.description = "Ensure that the keys are unique within a locale across all resource files";
34
+ this.sourceLocale = (options && options.sourceLocale) || "en-US";
35
+
36
+ this.ts = new TranslationSet();
37
+ }
38
+
39
+ getRuleType() {
40
+ return "resource";
41
+ }
42
+
43
+ /**
44
+ * @override
45
+ */
46
+ match(options) {
47
+ const { locale, resource, file } = options || {};
48
+
49
+ const hash = resource.hashKey();
50
+ const other = this.ts.get(hash);
51
+
52
+ if (other) {
53
+ let value = {
54
+ severity: "error",
55
+ id: resource.getKey(),
56
+ rule: this,
57
+ pathName: file,
58
+ highlight: `Also defined in this file: ${other.resfile}`,
59
+ description: `Key is not unique within locale ${locale}.`,
60
+ locale
61
+ };
62
+ if (typeof(options.lineNumber) !== 'undefined') {
63
+ value.lineNumber = options.lineNumber;
64
+ }
65
+ return new Result(value);
66
+ }
67
+
68
+ resource.resfile = file;
69
+ this.ts.add(resource);
70
+
71
+ // no result
72
+ return;
73
+ }
74
+ }
75
+
76
+ export default ResourceUniqueKeys;
@@ -0,0 +1,78 @@
1
+ /*
2
+ * utils.js - utility functions for the rules
3
+ *
4
+ * Copyright © 2022 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 { IntlMessageFormat } from 'intl-messageformat';
21
+
22
+ function processNode(node) {
23
+ let text = "";
24
+ switch (node.type) {
25
+ case 0:
26
+ text = node.value;
27
+ break;
28
+
29
+ // Actual parameter
30
+ case 1:
31
+ text = `{${node.value}}`;
32
+ break;
33
+
34
+ case 6:
35
+ if (node.options) {
36
+ text = Object.keys(node.options).map(category => {
37
+ return concatText(node.options[category].value);
38
+ }).join(" ");
39
+ }
40
+ break;
41
+ }
42
+
43
+ if (node.children) {
44
+ text += concatText(node.children);
45
+ }
46
+
47
+ return text;
48
+ }
49
+
50
+ function concatText(ast) {
51
+ if (!ast) return "";
52
+
53
+ let result = "";
54
+
55
+ if (Array.isArray(ast)) {
56
+ result += ast.map(node => {
57
+ return processNode(node);
58
+ }).join(" ");
59
+ } else if (typeof(ast) === "Object") {
60
+ result = processNode(node);
61
+ } else if (typeof(ast) === "string") {
62
+ result = ast;
63
+ } // else just ignore
64
+
65
+ return result;
66
+ }
67
+
68
+ export function stripPlurals(str, locale) {
69
+ try {
70
+ const imf = new IntlMessageFormat(str, locale);
71
+ const ast = imf.getAst();
72
+
73
+ return concatText(ast).replace(/\s+/g, " ");
74
+ } catch (e) {
75
+ // punt
76
+ return str;
77
+ }
78
+ }
package/src/walk.js ADDED
@@ -0,0 +1,146 @@
1
+ /*
2
+ * walk.js - walk a directory tree
3
+ *
4
+ * Copyright © 2022 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 fs from 'node:fs';
21
+ import path from 'node:path';
22
+ import log4js from 'log4js';
23
+ import mm from 'micromatch';
24
+
25
+ import SourceFile from './SourceFile.js';
26
+
27
+ const logger = log4js.getLogger("ilib-lint.walk");
28
+
29
+ /**
30
+ * Recursively walk a directory and return a list of files and directories
31
+ * within that directory. The walk is controlled via a list of exclude and
32
+ * include patterns. Each pattern should be a micromatch pattern like this:
33
+ *
34
+ * <code>
35
+ * "*.json"
36
+ * </code>
37
+ *
38
+ * The full path to every file and directory in the top-level directory will
39
+ * be included, unless it matches an exclude pattern, it which case, it will be
40
+ * excluded from the output. However, if the path
41
+ * also matches an include pattern, it will still be included nonetheless. The
42
+ * idea is that you can exclude a whole category of files (like all json files),
43
+ * but include specific ones. For example, you may exclude all json files, but
44
+ * still want to include the "config.json" file.<p>
45
+
46
+ * The options parameter may include any of the following optional properties:
47
+ *
48
+ * <ul>
49
+ * <li><i>quiet</i> (boolean) - whether or not to give output while walking
50
+ * the directory tree
51
+ * <li><i>excludes</i> (Array of strings) - A list of micromatch patterns to
52
+ * exclude from the output. If a pattern matches a directory, that directory
53
+ * will not be recursively searched.
54
+ * <li><i>includes</i> (Array of strings) - A list of micromatch patterns to
55
+ * include in the walk. If a pattern matches both an exclude and an include, the
56
+ * include will override the exclude.
57
+ * </ul>
58
+ *
59
+ * @param {String} root Directory to walk
60
+ * @param {Object} options Options controlling how this walk happens. (See
61
+ * the description for more details.)
62
+ * @returns {Array.<SourceFile>} an array of file names in the directory, filtered
63
+ * by the the excludes and includes list
64
+ */
65
+ function walk(root, options) {
66
+ let results = [], projectRoot = false, newProject, list;
67
+
68
+ if (typeof(root) !== "string") {
69
+ return results;
70
+ }
71
+
72
+ const { config, quiet = false } = options || {};
73
+ const includes = config && config.paths ? Object.keys(config.paths) : ["**"];
74
+ let excludes = config && config.excludes;
75
+ let pathName, relPath, included, stat, glob;
76
+
77
+ try {
78
+ if (fs.existsSync(root)) {
79
+ stat = fs.statSync(root);
80
+ if (stat && stat.isDirectory()) {
81
+ list = fs.readdirSync(root);
82
+ if (!quiet) logger.trace("Searching " + root);
83
+
84
+ if (list && list.length !== 0) {
85
+ list.sort().forEach((file) => {
86
+ if (file === "." || file === "..") {
87
+ return;
88
+ }
89
+
90
+ pathName = path.join(root, file);
91
+ included = true;
92
+
93
+ if (excludes) {
94
+ if (!quiet) logger.trace(`There are excludes. Relpath is ${pathName}`);
95
+ included = !mm.isMatch(pathName, excludes);
96
+ }
97
+
98
+ if (included) {
99
+ results = results.concat(walk(pathName, options));
100
+ }
101
+ });
102
+ }
103
+ } else {
104
+ // file
105
+ included = false;
106
+
107
+ if (includes) {
108
+ if (!quiet) logger.trace(`There are includes.`);
109
+ mm.match(root, includes, {
110
+ onMatch: (params) => {
111
+ if (!glob && params.isMatch) {
112
+ glob = params.glob;
113
+ excludes = config && ((config.paths && config.paths[glob] && config.paths[glob].excludes) || excludes);
114
+ included = excludes ? !mm.isMatch(root, excludes) : true;
115
+ }
116
+ }
117
+ });
118
+ }
119
+
120
+ if (included) {
121
+ if (!quiet) logger.trace(`${pathName} ... included`);
122
+ glob = glob || "**";
123
+ results.push(new SourceFile({
124
+ filePath: root,
125
+ settings: config.paths && config.paths[glob]
126
+ }));
127
+ } else {
128
+ if (!quiet) logger.trace(`${pathName} ... excluded`);
129
+ }
130
+ }
131
+ } else {
132
+ if (!quiet) logger.warn(`File ${pathName} does not exist.`);
133
+ }
134
+ } catch (e) {
135
+ // if the readdirSync did not work, it's maybe a file?
136
+ if (fs.existsSync(root)) {
137
+ return [new SourceFile({
138
+ filePath: root
139
+ })];
140
+ }
141
+ }
142
+
143
+ return results;
144
+ }
145
+
146
+ export default walk;