@markuplint/file-resolver 4.0.0-alpha.1 → 4.0.0-alpha.11
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/LICENSE +1 -1
- package/lib/auto-load-rules.js +6 -6
- package/lib/config-load-error.d.ts +6 -0
- package/lib/config-load-error.js +8 -0
- package/lib/config-provider.d.ts +4 -1
- package/lib/config-provider.js +106 -169
- package/lib/cosmiconfig.d.ts +4 -3
- package/lib/cosmiconfig.js +16 -17
- package/lib/debug.d.ts +3 -0
- package/lib/debug.js +2 -0
- package/lib/force-import-json-in-module.d.ts +1 -0
- package/lib/force-import-json-in-module.js +31 -0
- package/lib/general-import.d.ts +1 -0
- package/lib/general-import.js +40 -0
- package/lib/get-preset.d.ts +2 -0
- package/lib/get-preset.js +13 -0
- package/lib/is-plugin-module-name.d.ts +1 -0
- package/lib/is-plugin-module-name.js +3 -0
- package/lib/is-preset-module-name.d.ts +1 -0
- package/lib/is-preset-module-name.js +3 -0
- package/lib/ml-file/get-files.d.ts +1 -1
- package/lib/ml-file/get-files.js +4 -2
- package/lib/ml-file/ml-file.js +16 -6
- package/lib/module-exists.d.ts +1 -0
- package/lib/module-exists.js +49 -0
- package/lib/path-to-abs-or-name.d.ts +1 -0
- package/lib/path-to-abs-or-name.js +38 -0
- package/lib/resolve-files.d.ts +1 -1
- package/lib/resolve-files.js +2 -2
- package/lib/resolve-name-or-abs-path.d.ts +1 -0
- package/lib/resolve-name-or-abs-path.js +14 -0
- package/lib/resolve-parser.d.ts +2 -2
- package/lib/resolve-parser.js +11 -2
- package/lib/resolve-plugins.js +22 -26
- package/lib/resolve-rules.js +10 -10
- package/lib/resolve-specs.js +6 -2
- package/lib/utils.js +1 -1
- package/package.json +16 -17
package/LICENSE
CHANGED
package/lib/auto-load-rules.js
CHANGED
|
@@ -20,8 +20,8 @@ export async function autoLoadRules(ruleset) {
|
|
|
20
20
|
seed = null;
|
|
21
21
|
}
|
|
22
22
|
}
|
|
23
|
-
catch (
|
|
24
|
-
errors.push(
|
|
23
|
+
catch (error) {
|
|
24
|
+
errors.push(error);
|
|
25
25
|
}
|
|
26
26
|
if (seed) {
|
|
27
27
|
const rule = new MLRule({
|
|
@@ -39,8 +39,8 @@ export async function autoLoadRules(ruleset) {
|
|
|
39
39
|
seed = null;
|
|
40
40
|
}
|
|
41
41
|
}
|
|
42
|
-
catch (
|
|
43
|
-
errors.push(
|
|
42
|
+
catch (error) {
|
|
43
|
+
errors.push(error);
|
|
44
44
|
}
|
|
45
45
|
if (seed) {
|
|
46
46
|
const rule = new MLRule({
|
|
@@ -54,8 +54,8 @@ export async function autoLoadRules(ruleset) {
|
|
|
54
54
|
}
|
|
55
55
|
return {
|
|
56
56
|
// Clone
|
|
57
|
-
rules: rules
|
|
57
|
+
rules: [...rules],
|
|
58
58
|
// Clone
|
|
59
|
-
errors: errors
|
|
59
|
+
errors: [...errors],
|
|
60
60
|
};
|
|
61
61
|
}
|
package/lib/config-provider.d.ts
CHANGED
|
@@ -4,12 +4,15 @@ import type { Config } from '@markuplint/ml-config';
|
|
|
4
4
|
import type { Nullable } from '@markuplint/shared';
|
|
5
5
|
export declare class ConfigProvider {
|
|
6
6
|
#private;
|
|
7
|
+
recursiveLoad(key: string, cache: boolean, referrer: string, depth?: number): Promise<{
|
|
8
|
+
stack: Set<string>;
|
|
9
|
+
errs: Error[];
|
|
10
|
+
}>;
|
|
7
11
|
resolve(targetFile: Readonly<MLFile>, names: readonly Nullable<string>[], cache?: boolean): Promise<ConfigSet>;
|
|
8
12
|
search(targetFile: Readonly<MLFile>): Promise<string | null>;
|
|
9
13
|
set(config: Config, key?: string): string;
|
|
10
14
|
private _load;
|
|
11
15
|
private _mergeConfigs;
|
|
12
16
|
private _pathResolve;
|
|
13
|
-
private _recursiveLoad;
|
|
14
17
|
private _validateConfig;
|
|
15
18
|
}
|
package/lib/config-provider.js
CHANGED
|
@@ -1,16 +1,26 @@
|
|
|
1
|
+
var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
|
|
2
|
+
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
|
|
3
|
+
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
|
|
4
|
+
return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
|
|
5
|
+
};
|
|
1
6
|
var _ConfigProvider_cache, _ConfigProvider_held, _ConfigProvider_recursiveLoadKeyAndDepth, _ConfigProvider_store;
|
|
2
|
-
import { __classPrivateFieldGet } from "tslib";
|
|
3
|
-
import { createRequire } from 'node:module';
|
|
4
7
|
import path from 'node:path';
|
|
5
8
|
import { mergeConfig } from '@markuplint/ml-config';
|
|
6
|
-
import { getPreset } from '@markuplint/ml-core';
|
|
7
9
|
import { ConfigParserError } from '@markuplint/parser-utils';
|
|
8
10
|
import { InvalidSelectorError, createSelector } from '@markuplint/selector';
|
|
9
11
|
import { nonNullableFilter, toNoEmptyStringArrayFromStringOrArray } from '@markuplint/shared';
|
|
12
|
+
import { ConfigLoadError } from './config-load-error.js';
|
|
10
13
|
import { load as loadConfig, search } from './cosmiconfig.js';
|
|
14
|
+
import { log } from './debug.js';
|
|
15
|
+
import { generalImport } from './general-import.js';
|
|
16
|
+
import { getPreset } from './get-preset.js';
|
|
17
|
+
import { isPluginModuleName } from './is-plugin-module-name.js';
|
|
18
|
+
import { isPresetModuleName } from './is-preset-module-name.js';
|
|
19
|
+
import { moduleExists } from './module-exists.js';
|
|
20
|
+
import { relPathToNameOrAbsPath } from './path-to-abs-or-name.js';
|
|
11
21
|
import { cacheClear, resolvePlugins } from './resolve-plugins.js';
|
|
12
22
|
import { fileExists, uuid } from './utils.js';
|
|
13
|
-
const
|
|
23
|
+
const cpLog = log.extend('config-provider');
|
|
14
24
|
const KEY_SEPARATOR = '__ML_CONFIG_MERGE__';
|
|
15
25
|
export class ConfigProvider {
|
|
16
26
|
constructor() {
|
|
@@ -19,6 +29,44 @@ export class ConfigProvider {
|
|
|
19
29
|
_ConfigProvider_recursiveLoadKeyAndDepth.set(this, new Map());
|
|
20
30
|
_ConfigProvider_store.set(this, new Map());
|
|
21
31
|
}
|
|
32
|
+
async recursiveLoad(key, cache, referrer, depth = 1) {
|
|
33
|
+
const stack = new Set();
|
|
34
|
+
const errs = [];
|
|
35
|
+
const ancestorDepth = __classPrivateFieldGet(this, _ConfigProvider_recursiveLoadKeyAndDepth, "f").get(key);
|
|
36
|
+
if (ancestorDepth != null && ancestorDepth < depth) {
|
|
37
|
+
return {
|
|
38
|
+
stack,
|
|
39
|
+
errs: [new CircularReferenceError(`Circular reference detected: ${key}`)],
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
__classPrivateFieldGet(this, _ConfigProvider_recursiveLoadKeyAndDepth, "f").set(key, depth);
|
|
43
|
+
let config = __classPrivateFieldGet(this, _ConfigProvider_store, "f").get(key);
|
|
44
|
+
if (!config) {
|
|
45
|
+
config = await this._load(key, cache, referrer);
|
|
46
|
+
}
|
|
47
|
+
if (!config) {
|
|
48
|
+
return { stack, errs: [] };
|
|
49
|
+
}
|
|
50
|
+
if (config instanceof ConfigLoadError) {
|
|
51
|
+
stack.add(config.filePath);
|
|
52
|
+
return {
|
|
53
|
+
stack,
|
|
54
|
+
errs: [config],
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
const depKeys = config.extends === null ? null : toNoEmptyStringArrayFromStringOrArray(config.extends);
|
|
58
|
+
if (depKeys) {
|
|
59
|
+
for (const depKey of depKeys) {
|
|
60
|
+
const keys = await this.recursiveLoad(depKey, cache, key, depth + 1);
|
|
61
|
+
for (const key of keys.stack) {
|
|
62
|
+
stack.add(key);
|
|
63
|
+
}
|
|
64
|
+
errs.push(...keys.errs);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
stack.add(key);
|
|
68
|
+
return { stack, errs };
|
|
69
|
+
}
|
|
22
70
|
async resolve(targetFile, names, cache = true) {
|
|
23
71
|
if (!cache) {
|
|
24
72
|
__classPrivateFieldGet(this, _ConfigProvider_store, "f").clear();
|
|
@@ -31,8 +79,8 @@ export class ConfigProvider {
|
|
|
31
79
|
if (currentConfig) {
|
|
32
80
|
return currentConfig;
|
|
33
81
|
}
|
|
34
|
-
let configSet = await this._mergeConfigs(keys, cache);
|
|
35
|
-
const filePath =
|
|
82
|
+
let configSet = await this._mergeConfigs(keys, cache, targetFile.path);
|
|
83
|
+
const filePath = [...configSet.files].reverse()[0];
|
|
36
84
|
if (!filePath) {
|
|
37
85
|
throw new ConfigParserError('Config file not found', {
|
|
38
86
|
filePath: targetFile.path,
|
|
@@ -42,7 +90,7 @@ export class ConfigProvider {
|
|
|
42
90
|
configSet.errs.push(...errors);
|
|
43
91
|
const plugins = await resolvePlugins(configSet.config.plugins);
|
|
44
92
|
if (__classPrivateFieldGet(this, _ConfigProvider_held, "f").size > 0) {
|
|
45
|
-
const extendHelds =
|
|
93
|
+
const extendHelds = [...__classPrivateFieldGet(this, _ConfigProvider_held, "f").values()];
|
|
46
94
|
for (const held of extendHelds) {
|
|
47
95
|
const [, prefix, namespace, name] = held.match(/^([a-z]+:)([^/]+)(?:\/(.+))?$/) ?? [];
|
|
48
96
|
switch (prefix) {
|
|
@@ -56,7 +104,7 @@ export class ConfigProvider {
|
|
|
56
104
|
}
|
|
57
105
|
}
|
|
58
106
|
}
|
|
59
|
-
configSet = await this._mergeConfigs([...keys, ...extendHelds], cache);
|
|
107
|
+
configSet = await this._mergeConfigs([...keys, ...extendHelds], cache, targetFile.path);
|
|
60
108
|
__classPrivateFieldGet(this, _ConfigProvider_held, "f").clear();
|
|
61
109
|
}
|
|
62
110
|
// Resolves `overrides`
|
|
@@ -80,16 +128,21 @@ export class ConfigProvider {
|
|
|
80
128
|
return result;
|
|
81
129
|
}
|
|
82
130
|
async search(targetFile) {
|
|
83
|
-
|
|
131
|
+
const isExists = await targetFile.dirExists();
|
|
132
|
+
cpLog('search: %s', targetFile.path);
|
|
133
|
+
cpLog('isExists: %s', isExists);
|
|
134
|
+
if (!isExists) {
|
|
84
135
|
return null;
|
|
85
136
|
}
|
|
86
137
|
const res = await search(targetFile.path, false);
|
|
138
|
+
cpLog('searched config: %O', res);
|
|
87
139
|
if (!res) {
|
|
88
140
|
return null;
|
|
89
141
|
}
|
|
90
142
|
const { filePath, config } = res;
|
|
91
143
|
const pathResolvedConfig = await this._pathResolve(config, filePath);
|
|
92
144
|
__classPrivateFieldGet(this, _ConfigProvider_store, "f").set(filePath, pathResolvedConfig);
|
|
145
|
+
cpLog('Store key: %s', filePath);
|
|
93
146
|
return filePath;
|
|
94
147
|
}
|
|
95
148
|
set(config, key) {
|
|
@@ -97,51 +150,51 @@ export class ConfigProvider {
|
|
|
97
150
|
__classPrivateFieldGet(this, _ConfigProvider_store, "f").set(key, config);
|
|
98
151
|
return key;
|
|
99
152
|
}
|
|
100
|
-
async _load(filePath, cache) {
|
|
153
|
+
async _load(filePath, cache, referrer) {
|
|
101
154
|
const entity = __classPrivateFieldGet(this, _ConfigProvider_store, "f").get(filePath);
|
|
102
155
|
if (entity) {
|
|
103
156
|
return entity;
|
|
104
157
|
}
|
|
105
|
-
if (
|
|
158
|
+
if (isPresetModuleName(filePath)) {
|
|
106
159
|
const [, name] = filePath.match(/^markuplint:(.+)$/i) ?? [];
|
|
107
160
|
const config = await getPreset(name ?? filePath);
|
|
108
161
|
const pathResolvedConfig = await this._pathResolve(config, filePath);
|
|
109
162
|
__classPrivateFieldGet(this, _ConfigProvider_store, "f").set(filePath, pathResolvedConfig);
|
|
110
163
|
return pathResolvedConfig;
|
|
111
164
|
}
|
|
112
|
-
if (
|
|
165
|
+
if (isPluginModuleName(filePath)) {
|
|
113
166
|
__classPrivateFieldGet(this, _ConfigProvider_held, "f").add(filePath);
|
|
114
|
-
return
|
|
167
|
+
return;
|
|
115
168
|
}
|
|
116
169
|
if (!(await moduleExists(filePath)) && !path.isAbsolute(filePath)) {
|
|
117
170
|
throw new TypeError(`${filePath} is not an absolute path`);
|
|
118
171
|
}
|
|
119
|
-
const config = await load(filePath, cache);
|
|
120
|
-
if (
|
|
121
|
-
return
|
|
172
|
+
const config = await load(filePath, cache, referrer);
|
|
173
|
+
if (config instanceof ConfigLoadError) {
|
|
174
|
+
return config;
|
|
122
175
|
}
|
|
123
176
|
const pathResolvedConfig = await this._pathResolve(config, filePath);
|
|
124
177
|
__classPrivateFieldGet(this, _ConfigProvider_store, "f").set(filePath, pathResolvedConfig);
|
|
125
178
|
return pathResolvedConfig;
|
|
126
179
|
}
|
|
127
|
-
async _mergeConfigs(keys, cache) {
|
|
180
|
+
async _mergeConfigs(keys, cache, referrer) {
|
|
128
181
|
const resolvedKeys = new Set();
|
|
129
182
|
const errs = [];
|
|
130
183
|
for (const key of keys) {
|
|
131
184
|
__classPrivateFieldGet(this, _ConfigProvider_recursiveLoadKeyAndDepth, "f").clear();
|
|
132
|
-
const keySet = await this.
|
|
185
|
+
const keySet = await this.recursiveLoad(key, cache, referrer);
|
|
133
186
|
for (const k of keySet.stack) {
|
|
134
187
|
resolvedKeys.add(k);
|
|
135
188
|
}
|
|
136
|
-
|
|
137
|
-
errs.push(...keySet.errs);
|
|
138
|
-
}
|
|
189
|
+
errs.push(...keySet.errs);
|
|
139
190
|
}
|
|
140
|
-
const configs =
|
|
141
|
-
.map(name => __classPrivateFieldGet(this, _ConfigProvider_store, "f").get(name))
|
|
142
|
-
.filter(nonNullableFilter);
|
|
191
|
+
const configs = [...resolvedKeys].map(name => __classPrivateFieldGet(this, _ConfigProvider_store, "f").get(name)).filter(nonNullableFilter);
|
|
143
192
|
let resultConfig = {};
|
|
144
193
|
for (const config of configs) {
|
|
194
|
+
if (config instanceof ConfigLoadError) {
|
|
195
|
+
errs.push(config);
|
|
196
|
+
continue;
|
|
197
|
+
}
|
|
145
198
|
resultConfig = mergeConfig(resultConfig, config);
|
|
146
199
|
}
|
|
147
200
|
return {
|
|
@@ -154,167 +207,51 @@ export class ConfigProvider {
|
|
|
154
207
|
const dir = path.dirname(filePath);
|
|
155
208
|
return {
|
|
156
209
|
...config,
|
|
157
|
-
extends: await
|
|
158
|
-
plugins: await
|
|
159
|
-
parser: await
|
|
160
|
-
specs: await
|
|
161
|
-
excludeFiles: await
|
|
162
|
-
overrides: await
|
|
210
|
+
extends: await relPathToNameOrAbsPath(dir, config.extends),
|
|
211
|
+
plugins: await relPathToNameOrAbsPath(dir, config.plugins, ['name']),
|
|
212
|
+
parser: await relPathToNameOrAbsPath(dir, config.parser),
|
|
213
|
+
specs: await relPathToNameOrAbsPath(dir, config.specs),
|
|
214
|
+
excludeFiles: await relPathToNameOrAbsPath(dir, config.excludeFiles),
|
|
215
|
+
overrides: await relPathToNameOrAbsPath(dir, config.overrides, undefined, true),
|
|
163
216
|
};
|
|
164
217
|
}
|
|
165
|
-
async _recursiveLoad(key, cache, depth = 1) {
|
|
166
|
-
const stack = new Set();
|
|
167
|
-
const errs = [];
|
|
168
|
-
const ancestorDepth = __classPrivateFieldGet(this, _ConfigProvider_recursiveLoadKeyAndDepth, "f").get(key);
|
|
169
|
-
if (ancestorDepth != null && ancestorDepth < depth) {
|
|
170
|
-
return {
|
|
171
|
-
stack,
|
|
172
|
-
errs: [new CircularReferenceError(`Circular reference detected: ${key}`)],
|
|
173
|
-
};
|
|
174
|
-
}
|
|
175
|
-
__classPrivateFieldGet(this, _ConfigProvider_recursiveLoadKeyAndDepth, "f").set(key, depth);
|
|
176
|
-
let config = __classPrivateFieldGet(this, _ConfigProvider_store, "f").get(key) ?? null;
|
|
177
|
-
if (!config) {
|
|
178
|
-
config = await this._load(key, cache);
|
|
179
|
-
}
|
|
180
|
-
if (!config) {
|
|
181
|
-
return { stack, errs: null };
|
|
182
|
-
}
|
|
183
|
-
const depKeys = config.extends !== null ? toNoEmptyStringArrayFromStringOrArray(config.extends) : null;
|
|
184
|
-
if (depKeys) {
|
|
185
|
-
for (const depKey of depKeys) {
|
|
186
|
-
const keys = await this._recursiveLoad(depKey, cache, depth + 1);
|
|
187
|
-
for (const key of keys.stack) {
|
|
188
|
-
stack.add(key);
|
|
189
|
-
}
|
|
190
|
-
if (keys.errs) {
|
|
191
|
-
errs.push(...keys.errs);
|
|
192
|
-
}
|
|
193
|
-
}
|
|
194
|
-
}
|
|
195
|
-
stack.add(key);
|
|
196
|
-
return { stack, errs };
|
|
197
|
-
}
|
|
198
218
|
_validateConfig(config, filePath) {
|
|
199
219
|
const errors = [];
|
|
200
|
-
config.nodeRules
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
220
|
+
if (config.nodeRules)
|
|
221
|
+
for (const rule of config.nodeRules) {
|
|
222
|
+
if (rule.selector) {
|
|
223
|
+
try {
|
|
224
|
+
createSelector(rule.selector);
|
|
225
|
+
}
|
|
226
|
+
catch (error) {
|
|
227
|
+
if (error instanceof InvalidSelectorError) {
|
|
228
|
+
errors.push(new ConfigParserError(error.message, {
|
|
229
|
+
filePath,
|
|
230
|
+
raw: rule.selector,
|
|
231
|
+
}));
|
|
232
|
+
}
|
|
211
233
|
}
|
|
212
234
|
}
|
|
213
235
|
}
|
|
214
|
-
});
|
|
215
236
|
return errors;
|
|
216
237
|
}
|
|
217
238
|
}
|
|
218
239
|
_ConfigProvider_cache = new WeakMap(), _ConfigProvider_held = new WeakMap(), _ConfigProvider_recursiveLoadKeyAndDepth = new WeakMap(), _ConfigProvider_store = new WeakMap();
|
|
219
|
-
async function load(filePath, cache) {
|
|
240
|
+
async function load(filePath, cache, referrer) {
|
|
220
241
|
if (!fileExists(filePath) && (await moduleExists(filePath))) {
|
|
221
|
-
const
|
|
222
|
-
const config = mod?.default ?? null;
|
|
242
|
+
const config = (await generalImport(filePath)) ?? new ConfigLoadError('Module is not found', filePath, referrer);
|
|
223
243
|
return config;
|
|
224
244
|
}
|
|
225
|
-
const res = await loadConfig(filePath, !cache)
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
}
|
|
229
|
-
return res.config;
|
|
230
|
-
}
|
|
231
|
-
async function pathResolve(dir, filePath, resolveProps, resolveKey = false) {
|
|
232
|
-
if (filePath == null) {
|
|
233
|
-
// @ts-ignore
|
|
234
|
-
return undefined;
|
|
235
|
-
}
|
|
236
|
-
if (typeof filePath === 'string') {
|
|
237
|
-
// @ts-ignore
|
|
238
|
-
return resolve(dir, filePath);
|
|
239
|
-
}
|
|
240
|
-
if (Array.isArray(filePath)) {
|
|
241
|
-
// @ts-ignore
|
|
242
|
-
return Promise.all(filePath.map(fp => pathResolve(dir, fp, resolveProps)));
|
|
243
|
-
}
|
|
244
|
-
const res = {};
|
|
245
|
-
for (const [key, fp] of Object.entries(filePath)) {
|
|
246
|
-
let _key = key;
|
|
247
|
-
if (resolveKey) {
|
|
248
|
-
_key = await resolve(dir, key);
|
|
249
|
-
}
|
|
250
|
-
if (typeof fp === 'string') {
|
|
251
|
-
if (!resolveProps) {
|
|
252
|
-
res[_key] = await resolve(dir, fp);
|
|
253
|
-
}
|
|
254
|
-
else if (resolveProps.includes(key)) {
|
|
255
|
-
res[_key] = await resolve(dir, fp);
|
|
256
|
-
}
|
|
257
|
-
else {
|
|
258
|
-
res[_key] = fp;
|
|
259
|
-
}
|
|
245
|
+
const res = await loadConfig(filePath, !cache, referrer).catch((error) => {
|
|
246
|
+
if (error instanceof ConfigLoadError) {
|
|
247
|
+
return error;
|
|
260
248
|
}
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
// @ts-ignore
|
|
266
|
-
return res;
|
|
267
|
-
}
|
|
268
|
-
async function resolve(dir, pathOrModName) {
|
|
269
|
-
if ((await moduleExists(pathOrModName)) || isPreset(pathOrModName) || isPlugin(pathOrModName)) {
|
|
270
|
-
return pathOrModName;
|
|
271
|
-
}
|
|
272
|
-
const bangAndPath = /^(!)(.*)/.exec(pathOrModName) ?? [];
|
|
273
|
-
const bang = bangAndPath[1] ?? '';
|
|
274
|
-
const pathname = bangAndPath[2] ?? pathOrModName;
|
|
275
|
-
const absPath = path.resolve(dir, pathname);
|
|
276
|
-
return bang + absPath;
|
|
277
|
-
}
|
|
278
|
-
async function moduleExists(name) {
|
|
279
|
-
try {
|
|
280
|
-
await import(name);
|
|
249
|
+
throw error;
|
|
250
|
+
});
|
|
251
|
+
if (res instanceof ConfigLoadError) {
|
|
252
|
+
return res;
|
|
281
253
|
}
|
|
282
|
-
|
|
283
|
-
if (err instanceof Error) {
|
|
284
|
-
if (/^Parse failure/i.test(err.message)) {
|
|
285
|
-
return true;
|
|
286
|
-
}
|
|
287
|
-
}
|
|
288
|
-
try {
|
|
289
|
-
require.resolve(name);
|
|
290
|
-
}
|
|
291
|
-
catch (err) {
|
|
292
|
-
if (
|
|
293
|
-
// @ts-ignore
|
|
294
|
-
'code' in err &&
|
|
295
|
-
// @ts-ignore
|
|
296
|
-
err.code === 'ERR_PACKAGE_PATH_NOT_EXPORTED') {
|
|
297
|
-
// Even if there are issues with the fields,
|
|
298
|
-
// assume that the module exists and return true.
|
|
299
|
-
return true;
|
|
300
|
-
}
|
|
301
|
-
if (
|
|
302
|
-
// @ts-ignore
|
|
303
|
-
'code' in err &&
|
|
304
|
-
// @ts-ignore
|
|
305
|
-
err.code === 'MODULE_NOT_FOUND') {
|
|
306
|
-
return false;
|
|
307
|
-
}
|
|
308
|
-
throw err;
|
|
309
|
-
}
|
|
310
|
-
}
|
|
311
|
-
return true;
|
|
312
|
-
}
|
|
313
|
-
function isPreset(name) {
|
|
314
|
-
return /^markuplint:/i.test(name);
|
|
315
|
-
}
|
|
316
|
-
function isPlugin(name) {
|
|
317
|
-
return /^plugin:/i.test(name);
|
|
254
|
+
return res.config;
|
|
318
255
|
}
|
|
319
256
|
class CircularReferenceError extends ReferenceError {
|
|
320
257
|
constructor() {
|
package/lib/cosmiconfig.d.ts
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import type { LoaderSync } from 'cosmiconfig';
|
|
2
|
+
import { ConfigLoadError } from './config-load-error.js';
|
|
2
3
|
type CosmiConfig = ReturnType<LoaderSync>;
|
|
3
|
-
export declare function search<T = CosmiConfig>(
|
|
4
|
+
export declare function search<T = CosmiConfig>(filePath: string, cacheClear: boolean): Promise<{
|
|
4
5
|
filePath: string;
|
|
5
6
|
config: T;
|
|
6
7
|
} | null>;
|
|
7
|
-
export declare function load<T = CosmiConfig>(filePath: string, cacheClear: boolean): Promise<{
|
|
8
|
+
export declare function load<T = CosmiConfig>(filePath: string, cacheClear: boolean, referrer: string): Promise<ConfigLoadError | {
|
|
8
9
|
filePath: string;
|
|
9
10
|
config: T;
|
|
10
|
-
}
|
|
11
|
+
}>;
|
|
11
12
|
export {};
|
package/lib/cosmiconfig.js
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import path from 'node:path';
|
|
2
2
|
import { ConfigParserError } from '@markuplint/parser-utils';
|
|
3
3
|
import { cosmiconfig, defaultLoaders } from 'cosmiconfig';
|
|
4
|
-
import { TypeScriptLoader } from 'cosmiconfig-typescript-loader';
|
|
5
4
|
import { jsonc } from 'jsonc';
|
|
5
|
+
import { ConfigLoadError } from './config-load-error.js';
|
|
6
|
+
import { log } from './debug.js';
|
|
7
|
+
const searchLog = log.extend('search');
|
|
6
8
|
const explorer = cosmiconfig('markuplint', {
|
|
7
9
|
loaders: {
|
|
8
|
-
'.ts': TypeScriptLoader(),
|
|
9
10
|
noExt: ((path, content) => {
|
|
10
11
|
try {
|
|
11
12
|
return jsonc.parse(content);
|
|
@@ -18,13 +19,16 @@ const explorer = cosmiconfig('markuplint', {
|
|
|
18
19
|
}
|
|
19
20
|
}),
|
|
20
21
|
},
|
|
22
|
+
searchStrategy: 'project',
|
|
21
23
|
});
|
|
22
|
-
export async function search(
|
|
24
|
+
export async function search(filePath, cacheClear) {
|
|
23
25
|
if (cacheClear) {
|
|
24
26
|
explorer.clearCaches();
|
|
25
27
|
}
|
|
26
|
-
dir = path.dirname(
|
|
27
|
-
|
|
28
|
+
const dir = path.dirname(filePath);
|
|
29
|
+
searchLog('Search dir: %s', dir);
|
|
30
|
+
const result = await explorer.search(dir).catch(cacheConfigError(dir, filePath));
|
|
31
|
+
searchLog('Search result: %O', result);
|
|
28
32
|
if (!result || result.isEmpty) {
|
|
29
33
|
return null;
|
|
30
34
|
}
|
|
@@ -33,36 +37,31 @@ export async function search(dir, cacheClear) {
|
|
|
33
37
|
config: result.config,
|
|
34
38
|
};
|
|
35
39
|
}
|
|
36
|
-
export async function load(filePath, cacheClear) {
|
|
40
|
+
export async function load(filePath, cacheClear, referrer) {
|
|
37
41
|
if (cacheClear) {
|
|
38
42
|
explorer.clearCaches();
|
|
39
43
|
}
|
|
40
|
-
const result = await explorer.load(filePath).catch(cacheConfigError(filePath));
|
|
44
|
+
const result = await explorer.load(filePath).catch(cacheConfigError(filePath, referrer));
|
|
41
45
|
if (!result || result.isEmpty) {
|
|
42
|
-
return
|
|
46
|
+
return new ConfigLoadError('Config file is empty', filePath, referrer);
|
|
43
47
|
}
|
|
44
48
|
return {
|
|
45
49
|
filePath: result.filepath,
|
|
46
50
|
config: result.config,
|
|
47
51
|
};
|
|
48
52
|
}
|
|
49
|
-
|
|
50
|
-
constructor(message, filePath) {
|
|
51
|
-
super(message + ` in ${filePath}`);
|
|
52
|
-
this.name = 'ConfigLoadError';
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
function cacheConfigError(fileOrDirPath) {
|
|
53
|
+
function cacheConfigError(fileOrDirPath, referrer) {
|
|
56
54
|
return (reason) => {
|
|
57
55
|
if (reason instanceof Error) {
|
|
58
56
|
switch (reason.name) {
|
|
59
|
-
case 'YAMLException':
|
|
57
|
+
case 'YAMLException': {
|
|
60
58
|
throw new ConfigParserError(reason.message, {
|
|
61
59
|
// @ts-ignore
|
|
62
60
|
filePath: reason.filepath ?? fileOrDirPath,
|
|
63
61
|
});
|
|
62
|
+
}
|
|
64
63
|
}
|
|
65
|
-
throw new ConfigLoadError(reason.message, fileOrDirPath);
|
|
64
|
+
throw new ConfigLoadError(reason.message, fileOrDirPath, referrer);
|
|
66
65
|
}
|
|
67
66
|
throw reason;
|
|
68
67
|
};
|
package/lib/debug.d.ts
ADDED
package/lib/debug.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function forceImportJsonInModule(modPath: string): Promise<any>;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { readFile } from 'node:fs/promises';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { log } from './debug.js';
|
|
4
|
+
const fLog = log.extend('force-import-json-in-module');
|
|
5
|
+
export async function forceImportJsonInModule(modPath) {
|
|
6
|
+
const error = await import(modPath).catch(error => error);
|
|
7
|
+
if (error instanceof Error) {
|
|
8
|
+
fLog('Error in forceImportJsonInModule: %O', error);
|
|
9
|
+
if (!('code' in error)) {
|
|
10
|
+
throw error;
|
|
11
|
+
}
|
|
12
|
+
if (error.code !== 'ERR_IMPORT_ASSERTION_TYPE_MISSING') {
|
|
13
|
+
throw error;
|
|
14
|
+
}
|
|
15
|
+
const searchPath = /module\s"([^"]+)"\sneeds/i.exec(error.message);
|
|
16
|
+
const absPath = searchPath?.[1] ?? null;
|
|
17
|
+
fLog('Extract path: %s', absPath);
|
|
18
|
+
if (!absPath) {
|
|
19
|
+
throw error;
|
|
20
|
+
}
|
|
21
|
+
const normalizePath = absPath
|
|
22
|
+
.replace(/^file:\/\//, '')
|
|
23
|
+
.replaceAll('/', path.sep)
|
|
24
|
+
// Windows
|
|
25
|
+
.replace(/^[/\\][a-z]:/i, '');
|
|
26
|
+
fLog('Find JSON file path: %s', normalizePath);
|
|
27
|
+
const fileContent = await readFile(normalizePath, { encoding: 'utf8' });
|
|
28
|
+
return JSON.parse(fileContent);
|
|
29
|
+
}
|
|
30
|
+
return error.default ?? error;
|
|
31
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function generalImport<T>(name: string): Promise<T | null>;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { createRequire } from 'node:module';
|
|
2
|
+
import { log } from './debug.js';
|
|
3
|
+
const gLog = log.extend('general-import');
|
|
4
|
+
const cache = new Map();
|
|
5
|
+
const require = createRequire(import.meta.url);
|
|
6
|
+
export async function generalImport(name) {
|
|
7
|
+
if (cache.has(name)) {
|
|
8
|
+
return cache.get(name);
|
|
9
|
+
}
|
|
10
|
+
try {
|
|
11
|
+
const imported = await import(name);
|
|
12
|
+
const mod = imported?.default ?? imported ?? null;
|
|
13
|
+
cache.set(name, mod);
|
|
14
|
+
return mod;
|
|
15
|
+
}
|
|
16
|
+
catch (error) {
|
|
17
|
+
if (
|
|
18
|
+
// @ts-ignore
|
|
19
|
+
'code' in error &&
|
|
20
|
+
// @ts-ignore
|
|
21
|
+
error.code === 'ERR_IMPORT_ASSERTION_TYPE_MISSING') {
|
|
22
|
+
try {
|
|
23
|
+
const mod = require(name) ?? null;
|
|
24
|
+
cache.set(name, mod);
|
|
25
|
+
return mod;
|
|
26
|
+
}
|
|
27
|
+
catch (error) {
|
|
28
|
+
if (error instanceof Error && /^parse failure/i.test(error.message)) {
|
|
29
|
+
gLog('Error in `createRequire(import.meta.url)()`: %O', error);
|
|
30
|
+
cache.set(name, null);
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
gLog('Error in generalImport: %O', error);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
gLog('Error in `import()`: %O', error);
|
|
37
|
+
cache.set(name, null);
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { forceImportJsonInModule } from './force-import-json-in-module.js';
|
|
2
|
+
const cache = new Map();
|
|
3
|
+
export async function getPreset(name) {
|
|
4
|
+
if (cache.has(name)) {
|
|
5
|
+
return cache.get(name);
|
|
6
|
+
}
|
|
7
|
+
const json = await forceImportJsonInModule(`@markuplint/config-presets/preset.${name}.json`);
|
|
8
|
+
if (json instanceof Error) {
|
|
9
|
+
throw new ReferenceError(`Preset markuplint:${name} is not found`);
|
|
10
|
+
}
|
|
11
|
+
cache.set(name, json);
|
|
12
|
+
return json;
|
|
13
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function isPluginModuleName(name: string): boolean;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function isPresetModuleName(name: string): boolean;
|
package/lib/ml-file/get-files.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { glob } from 'glob';
|
|
2
|
+
import { minimatch } from 'minimatch';
|
|
2
3
|
import { getFile } from './get-file.js';
|
|
3
4
|
/**
|
|
4
5
|
* Get files
|
|
@@ -7,7 +8,8 @@ import { getFile } from './get-file.js';
|
|
|
7
8
|
*
|
|
8
9
|
* @param filePathOrGlob
|
|
9
10
|
*/
|
|
10
|
-
export async function getFiles(filePathOrGlob) {
|
|
11
|
+
export async function getFiles(filePathOrGlob, ignoreGlob) {
|
|
11
12
|
const fileList = await glob(filePathOrGlob, {}).catch(() => []);
|
|
12
|
-
|
|
13
|
+
const filtered = fileList.filter(fileName => !minimatch(fileName, ignoreGlob ?? ''));
|
|
14
|
+
return filtered.map(fileName => getFile(fileName));
|
|
13
15
|
}
|
package/lib/ml-file/ml-file.js
CHANGED
|
@@ -1,5 +1,15 @@
|
|
|
1
|
+
var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) {
|
|
2
|
+
if (kind === "m") throw new TypeError("Private method is not writable");
|
|
3
|
+
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
|
|
4
|
+
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it");
|
|
5
|
+
return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
|
|
6
|
+
};
|
|
7
|
+
var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
|
|
8
|
+
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
|
|
9
|
+
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
|
|
10
|
+
return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
|
|
11
|
+
};
|
|
1
12
|
var _MLFile_basename, _MLFile_code, _MLFile_dirname, _MLFile_stat, _MLFile_type;
|
|
2
|
-
import { __classPrivateFieldGet, __classPrivateFieldSet } from "tslib";
|
|
3
13
|
import { promises as fs } from 'node:fs';
|
|
4
14
|
import path from 'node:path';
|
|
5
15
|
import ignore from 'ignore';
|
|
@@ -96,7 +106,7 @@ export class MLFile {
|
|
|
96
106
|
__classPrivateFieldSet(this, _MLFile_code, code, "f");
|
|
97
107
|
}
|
|
98
108
|
async _fetch() {
|
|
99
|
-
const code = await fs.readFile(this.path, { encoding: '
|
|
109
|
+
const code = await fs.readFile(this.path, { encoding: 'utf8' });
|
|
100
110
|
__classPrivateFieldSet(this, _MLFile_code, code, "f");
|
|
101
111
|
return code;
|
|
102
112
|
}
|
|
@@ -113,15 +123,15 @@ async function stat(filePath) {
|
|
|
113
123
|
try {
|
|
114
124
|
return await fs.stat(filePath);
|
|
115
125
|
}
|
|
116
|
-
catch (
|
|
126
|
+
catch (error) {
|
|
117
127
|
if (
|
|
118
128
|
// @ts-ignore
|
|
119
|
-
'code' in
|
|
129
|
+
'code' in error &&
|
|
120
130
|
// @ts-ignore
|
|
121
|
-
|
|
131
|
+
error.code === 'ENOENT') {
|
|
122
132
|
return null;
|
|
123
133
|
}
|
|
124
|
-
throw
|
|
134
|
+
throw error;
|
|
125
135
|
}
|
|
126
136
|
}
|
|
127
137
|
function pathNormalize(filePath, relative = false) {
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function moduleExists(name: string): Promise<boolean>;
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { createRequire } from 'node:module';
|
|
2
|
+
import { log } from './debug.js';
|
|
3
|
+
const require = createRequire(import.meta.url);
|
|
4
|
+
const mLog = log.extend('module-exists');
|
|
5
|
+
export async function moduleExists(name) {
|
|
6
|
+
try {
|
|
7
|
+
await import(name);
|
|
8
|
+
}
|
|
9
|
+
catch (error) {
|
|
10
|
+
if (
|
|
11
|
+
// @ts-ignore
|
|
12
|
+
'code' in error &&
|
|
13
|
+
// @ts-ignore
|
|
14
|
+
error.code === 'ERR_IMPORT_ASSERTION_TYPE_MISSING') {
|
|
15
|
+
// It exists, but it is may be a JSON file.
|
|
16
|
+
mLog('Return true, but it caught Error in `import()`: %O', error);
|
|
17
|
+
return true;
|
|
18
|
+
}
|
|
19
|
+
if (error instanceof Error && /^parse failure/i.test(error.message)) {
|
|
20
|
+
// It exists, but it failed to parse.
|
|
21
|
+
mLog('Return true, but it caught Error in `import()`: %O', error);
|
|
22
|
+
return true;
|
|
23
|
+
}
|
|
24
|
+
try {
|
|
25
|
+
require.resolve(name);
|
|
26
|
+
}
|
|
27
|
+
catch (error) {
|
|
28
|
+
if (
|
|
29
|
+
// @ts-ignore
|
|
30
|
+
'code' in error &&
|
|
31
|
+
// @ts-ignore
|
|
32
|
+
error.code === 'ERR_PACKAGE_PATH_NOT_EXPORTED') {
|
|
33
|
+
// Even if there are issues with the fields,
|
|
34
|
+
// assume that the module exists and return true.
|
|
35
|
+
mLog('Return true, but it caught Error in `require.resolve()`: %O', error);
|
|
36
|
+
return true;
|
|
37
|
+
}
|
|
38
|
+
if (
|
|
39
|
+
// @ts-ignore
|
|
40
|
+
'code' in error &&
|
|
41
|
+
// @ts-ignore
|
|
42
|
+
error.code === 'MODULE_NOT_FOUND') {
|
|
43
|
+
return false;
|
|
44
|
+
}
|
|
45
|
+
throw error;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
return true;
|
|
49
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function relPathToNameOrAbsPath<T extends string | readonly (string | Record<string, unknown>)[] | Readonly<Record<string, unknown>> | undefined>(dir: string, filePath?: T, resolveProps?: readonly string[], resolveKey?: boolean): Promise<T>;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { resolveNameOrAbsPath } from './resolve-name-or-abs-path.js';
|
|
2
|
+
export async function relPathToNameOrAbsPath(dir, filePath, resolveProps, resolveKey = false) {
|
|
3
|
+
if (filePath == null) {
|
|
4
|
+
// @ts-ignore
|
|
5
|
+
return undefined;
|
|
6
|
+
}
|
|
7
|
+
if (typeof filePath === 'string') {
|
|
8
|
+
// @ts-ignore
|
|
9
|
+
return resolveNameOrAbsPath(dir, filePath);
|
|
10
|
+
}
|
|
11
|
+
if (Array.isArray(filePath)) {
|
|
12
|
+
// @ts-ignore
|
|
13
|
+
return Promise.all(filePath.map(fp => relPathToNameOrAbsPath(dir, fp, resolveProps)));
|
|
14
|
+
}
|
|
15
|
+
const res = {};
|
|
16
|
+
for (const [key, fp] of Object.entries(filePath)) {
|
|
17
|
+
let _key = key;
|
|
18
|
+
if (resolveKey) {
|
|
19
|
+
_key = await resolveNameOrAbsPath(dir, key);
|
|
20
|
+
}
|
|
21
|
+
if (typeof fp === 'string') {
|
|
22
|
+
if (!resolveProps) {
|
|
23
|
+
res[_key] = await resolveNameOrAbsPath(dir, fp);
|
|
24
|
+
}
|
|
25
|
+
else if (resolveProps.includes(key)) {
|
|
26
|
+
res[_key] = await resolveNameOrAbsPath(dir, fp);
|
|
27
|
+
}
|
|
28
|
+
else {
|
|
29
|
+
res[_key] = fp;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
else {
|
|
33
|
+
res[_key] = fp;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
// @ts-ignore
|
|
37
|
+
return res;
|
|
38
|
+
}
|
package/lib/resolve-files.d.ts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
import type { MLFile } from './ml-file/index.js';
|
|
2
2
|
import type { Target } from './types.js';
|
|
3
|
-
export declare function resolveFiles(targetList: readonly Readonly<Target>[]): Promise<MLFile[]>;
|
|
3
|
+
export declare function resolveFiles(targetList: readonly Readonly<Target>[], ignoreGlob?: string): Promise<MLFile[]>;
|
package/lib/resolve-files.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { getAnonymousFile, getFiles } from './ml-file/index.js';
|
|
2
|
-
export async function resolveFiles(targetList) {
|
|
2
|
+
export async function resolveFiles(targetList, ignoreGlob) {
|
|
3
3
|
const res = [];
|
|
4
4
|
for (const target of targetList) {
|
|
5
5
|
if (typeof target === 'string') {
|
|
6
|
-
const file = await getFiles(target);
|
|
6
|
+
const file = await getFiles(target, ignoreGlob);
|
|
7
7
|
res.push(...file);
|
|
8
8
|
continue;
|
|
9
9
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function resolveNameOrAbsPath(dir: string, pathOrModName: string): Promise<string>;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import { isPluginModuleName } from './is-plugin-module-name.js';
|
|
3
|
+
import { isPresetModuleName } from './is-preset-module-name.js';
|
|
4
|
+
import { moduleExists } from './module-exists.js';
|
|
5
|
+
export async function resolveNameOrAbsPath(dir, pathOrModName) {
|
|
6
|
+
if ((await moduleExists(pathOrModName)) || isPresetModuleName(pathOrModName) || isPluginModuleName(pathOrModName)) {
|
|
7
|
+
return pathOrModName;
|
|
8
|
+
}
|
|
9
|
+
const bangAndPath = /^(!)(.*)/.exec(pathOrModName) ?? [];
|
|
10
|
+
const bang = bangAndPath[1] ?? '';
|
|
11
|
+
const pathname = bangAndPath[2] ?? pathOrModName;
|
|
12
|
+
const absPath = path.resolve(dir, pathname);
|
|
13
|
+
return bang + absPath;
|
|
14
|
+
}
|
package/lib/resolve-parser.d.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import type { MLFile } from './ml-file/index.js';
|
|
2
|
-
import type { MLMarkupLanguageParser, ParserOptions } from '@markuplint/ml-ast';
|
|
2
|
+
import type { MLMarkupLanguageParser, MLParser, ParserOptions } from '@markuplint/ml-ast';
|
|
3
3
|
import type { ParserConfig } from '@markuplint/ml-config';
|
|
4
4
|
export declare function resolveParser(file: Readonly<MLFile>, parserConfig?: ParserConfig, parserOptions?: ParserOptions): Promise<{
|
|
5
5
|
parserModName: string;
|
|
6
|
-
parser: MLMarkupLanguageParser;
|
|
6
|
+
parser: MLParser | MLMarkupLanguageParser;
|
|
7
7
|
parserOptions: ParserOptions;
|
|
8
8
|
matched: boolean;
|
|
9
9
|
}>;
|
package/lib/resolve-parser.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import path from 'node:path';
|
|
2
|
+
import { generalImport } from './general-import.js';
|
|
2
3
|
import { toRegexp } from './utils.js';
|
|
3
4
|
const parsers = new Map();
|
|
4
5
|
export async function resolveParser(file, parserConfig, parserOptions) {
|
|
@@ -10,6 +11,7 @@ export async function resolveParser(file, parserConfig, parserOptions) {
|
|
|
10
11
|
let parserModName = '@markuplint/html-parser';
|
|
11
12
|
let matched = false;
|
|
12
13
|
for (const pattern of Object.keys(parserConfig)) {
|
|
14
|
+
// eslint-disable-next-line unicorn/prefer-regexp-test
|
|
13
15
|
if (path.basename(file.path).match(toRegexp(pattern))) {
|
|
14
16
|
const modName = parserConfig[pattern];
|
|
15
17
|
if (!modName) {
|
|
@@ -33,6 +35,13 @@ async function importParser(parserModName) {
|
|
|
33
35
|
if (entity) {
|
|
34
36
|
return entity;
|
|
35
37
|
}
|
|
36
|
-
const
|
|
37
|
-
|
|
38
|
+
const parserMod = await generalImport(parserModName);
|
|
39
|
+
if (!parserMod) {
|
|
40
|
+
throw new Error(`Parser module "${parserModName}" is not found.`);
|
|
41
|
+
}
|
|
42
|
+
// TODO: To be dropped in v5
|
|
43
|
+
if (!('parser' in parserMod)) {
|
|
44
|
+
return parserMod;
|
|
45
|
+
}
|
|
46
|
+
return parserMod.parser;
|
|
38
47
|
}
|
package/lib/resolve-plugins.js
CHANGED
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import { log } from './debug.js';
|
|
2
|
+
import { generalImport } from './general-import.js';
|
|
3
|
+
const pLog = log.extend('resolve-plugins');
|
|
1
4
|
const cache = new Map();
|
|
2
5
|
export async function resolvePlugins(pluginPaths) {
|
|
3
6
|
if (!pluginPaths) {
|
|
@@ -5,7 +8,7 @@ export async function resolvePlugins(pluginPaths) {
|
|
|
5
8
|
}
|
|
6
9
|
const plugins = await Promise.all(pluginPaths.map(p => importPlugin(p)));
|
|
7
10
|
// Clone
|
|
8
|
-
return plugins
|
|
11
|
+
return [...plugins];
|
|
9
12
|
}
|
|
10
13
|
export function cacheClear() {
|
|
11
14
|
cache.clear();
|
|
@@ -14,32 +17,32 @@ async function importPlugin(pluginPath) {
|
|
|
14
17
|
const config = getPluginConfig(pluginPath);
|
|
15
18
|
const cached = cache.get(config.name);
|
|
16
19
|
if (cached) {
|
|
20
|
+
pLog('Return from cache: %s', config.name);
|
|
17
21
|
return cached;
|
|
18
22
|
}
|
|
19
|
-
const pluginCreator = await
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
+
const pluginCreator = await generalImport(config.name);
|
|
24
|
+
let name = config.name;
|
|
25
|
+
let plugin = null;
|
|
26
|
+
if (typeof pluginCreator?.create === 'function' || pluginCreator?.name) {
|
|
27
|
+
plugin = {
|
|
28
|
+
name: pluginCreator.name,
|
|
29
|
+
...pluginCreator.create(config.settings),
|
|
23
30
|
};
|
|
31
|
+
name = plugin.name ?? name;
|
|
24
32
|
}
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
...pluginCreator.create(config.settings),
|
|
28
|
-
};
|
|
29
|
-
cache.set(plugin.name, plugin);
|
|
30
|
-
let name = plugin.name;
|
|
31
|
-
if (!name) {
|
|
32
|
-
name = config.name
|
|
33
|
-
.toLowerCase()
|
|
34
|
-
.replace(/^(?:markuplint-rule-|@markuplint\/rule-)/i, '')
|
|
35
|
-
.replace(/\s+|\/|\\|\./g, '-');
|
|
36
|
-
// eslint-disable-next-line no-console
|
|
37
|
-
console.info(`The plugin name became "${name}"`);
|
|
33
|
+
else if (pluginCreator) {
|
|
34
|
+
pLog('Invalid plugin: %s', config.name);
|
|
38
35
|
}
|
|
39
|
-
|
|
36
|
+
name = name
|
|
37
|
+
.toLowerCase()
|
|
38
|
+
.replace(/^(?:markuplint-rule-|@markuplint\/rule-)/i, '')
|
|
39
|
+
.replaceAll(/\s+|[./\\]/g, '-');
|
|
40
|
+
const result = {
|
|
40
41
|
...plugin,
|
|
41
42
|
name,
|
|
42
43
|
};
|
|
44
|
+
cache.set(name, result);
|
|
45
|
+
return result;
|
|
43
46
|
}
|
|
44
47
|
function getPluginConfig(pluginPath) {
|
|
45
48
|
if (typeof pluginPath === 'string') {
|
|
@@ -47,10 +50,3 @@ function getPluginConfig(pluginPath) {
|
|
|
47
50
|
}
|
|
48
51
|
return pluginPath;
|
|
49
52
|
}
|
|
50
|
-
async function failSafeImport(name) {
|
|
51
|
-
const res = await import(name).catch(e => e);
|
|
52
|
-
if ('code' in res && res === 'MODULE_NOT_FOUND') {
|
|
53
|
-
return null;
|
|
54
|
-
}
|
|
55
|
-
return res.default;
|
|
56
|
-
}
|
package/lib/resolve-rules.js
CHANGED
|
@@ -7,30 +7,30 @@ export async function resolveRules(plugins, ruleset, importPreset,
|
|
|
7
7
|
*/
|
|
8
8
|
autoLoad) {
|
|
9
9
|
const rules = importPreset ? await importPresetRules() : [];
|
|
10
|
-
|
|
10
|
+
for (const plugin of plugins) {
|
|
11
11
|
if (!plugin.rules) {
|
|
12
|
-
|
|
12
|
+
continue;
|
|
13
13
|
}
|
|
14
|
-
Object.entries(plugin.rules)
|
|
14
|
+
for (const [name, seed] of Object.entries(plugin.rules)) {
|
|
15
15
|
const rule = new MLRule({
|
|
16
16
|
name: `${plugin.name}/${name}`,
|
|
17
17
|
...seed,
|
|
18
18
|
});
|
|
19
19
|
rules.push(rule);
|
|
20
|
-
}
|
|
21
|
-
}
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
22
|
if (autoLoad) {
|
|
23
23
|
const { rules: additionalRules } = await autoLoadRules(ruleset);
|
|
24
|
-
|
|
24
|
+
for (const rule of additionalRules) {
|
|
25
25
|
rules.push(rule);
|
|
26
|
-
}
|
|
26
|
+
}
|
|
27
27
|
}
|
|
28
28
|
// Clone
|
|
29
|
-
return rules
|
|
29
|
+
return [...rules];
|
|
30
30
|
}
|
|
31
31
|
async function importPresetRules() {
|
|
32
32
|
if (cachedPresetRules) {
|
|
33
|
-
return cachedPresetRules
|
|
33
|
+
return [...cachedPresetRules];
|
|
34
34
|
}
|
|
35
35
|
const modName = '@markuplint/rules';
|
|
36
36
|
const mod = await import(modName);
|
|
@@ -44,5 +44,5 @@ async function importPresetRules() {
|
|
|
44
44
|
});
|
|
45
45
|
cachedPresetRules = ruleList;
|
|
46
46
|
// Clone
|
|
47
|
-
return ruleList
|
|
47
|
+
return [...ruleList];
|
|
48
48
|
}
|
package/lib/resolve-specs.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import path from 'node:path';
|
|
2
|
+
import { generalImport } from './general-import.js';
|
|
2
3
|
import { toRegexp } from './utils.js';
|
|
3
4
|
const caches = new Map();
|
|
4
5
|
/**
|
|
@@ -41,6 +42,7 @@ export async function resolveSpecs(filePath, specConfig) {
|
|
|
41
42
|
}
|
|
42
43
|
else {
|
|
43
44
|
for (const pattern of Object.keys(specConfig)) {
|
|
45
|
+
// eslint-disable-next-line unicorn/prefer-regexp-test
|
|
44
46
|
if (path.basename(filePath).match(toRegexp(pattern))) {
|
|
45
47
|
const specModName = specConfig[pattern];
|
|
46
48
|
if (!specModName) {
|
|
@@ -65,8 +67,10 @@ async function importSpecs(specModName) {
|
|
|
65
67
|
return spec;
|
|
66
68
|
}
|
|
67
69
|
}
|
|
68
|
-
const spec =
|
|
69
|
-
|
|
70
|
+
const spec = await generalImport(specModName);
|
|
71
|
+
if (!spec) {
|
|
72
|
+
throw new Error(`Spec "${specModName}" is not found.`);
|
|
73
|
+
}
|
|
70
74
|
caches.set(specModName, spec);
|
|
71
75
|
return spec;
|
|
72
76
|
}
|
package/lib/utils.js
CHANGED
|
@@ -9,7 +9,7 @@ export function fileExists(filePath) {
|
|
|
9
9
|
return fs.existsSync(filePath);
|
|
10
10
|
}
|
|
11
11
|
export function toRegexp(pattern) {
|
|
12
|
-
const matched = pattern.match(/^\/(.+)\/([
|
|
12
|
+
const matched = pattern.match(/^\/(.+)\/([gi]*)$/i);
|
|
13
13
|
if (matched && matched[1]) {
|
|
14
14
|
return new RegExp(matched[1], matched[2]);
|
|
15
15
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@markuplint/file-resolver",
|
|
3
|
-
"version": "4.0.0-alpha.
|
|
3
|
+
"version": "4.0.0-alpha.11",
|
|
4
4
|
"description": "The file resolver of markuplint",
|
|
5
5
|
"repository": "git@github.com:markuplint/markuplint.git",
|
|
6
6
|
"author": "Yusuke Hirao <yusukehirao@me.com>",
|
|
@@ -24,24 +24,23 @@
|
|
|
24
24
|
"clean": "tsc --build --clean"
|
|
25
25
|
},
|
|
26
26
|
"devDependencies": {
|
|
27
|
-
"@types/node": "20.
|
|
27
|
+
"@types/node": "20.11.10"
|
|
28
28
|
},
|
|
29
29
|
"dependencies": {
|
|
30
|
-
"@markuplint/html-parser": "4.0.0-alpha.
|
|
31
|
-
"@markuplint/ml-ast": "4.0.0-alpha.
|
|
32
|
-
"@markuplint/ml-config": "4.0.0-alpha.
|
|
33
|
-
"@markuplint/ml-core": "4.0.0-alpha.
|
|
34
|
-
"@markuplint/ml-spec": "4.0.0-alpha.
|
|
35
|
-
"@markuplint/parser-utils": "4.0.0-alpha.
|
|
36
|
-
"@markuplint/selector": "4.0.0-alpha.
|
|
37
|
-
"@markuplint/shared": "4.0.0-alpha.
|
|
38
|
-
"cosmiconfig": "^
|
|
39
|
-
"
|
|
40
|
-
"glob": "^10.3.
|
|
41
|
-
"ignore": "^5.
|
|
30
|
+
"@markuplint/html-parser": "4.0.0-alpha.11",
|
|
31
|
+
"@markuplint/ml-ast": "4.0.0-alpha.11",
|
|
32
|
+
"@markuplint/ml-config": "4.0.0-alpha.11",
|
|
33
|
+
"@markuplint/ml-core": "4.0.0-alpha.11",
|
|
34
|
+
"@markuplint/ml-spec": "4.0.0-alpha.11",
|
|
35
|
+
"@markuplint/parser-utils": "4.0.0-alpha.11",
|
|
36
|
+
"@markuplint/selector": "4.0.0-alpha.11",
|
|
37
|
+
"@markuplint/shared": "4.0.0-alpha.11",
|
|
38
|
+
"cosmiconfig": "^9.0.0",
|
|
39
|
+
"debug": "^4.3.4",
|
|
40
|
+
"glob": "^10.3.6",
|
|
41
|
+
"ignore": "^5.3.0",
|
|
42
42
|
"jsonc": "^2.0.0",
|
|
43
|
-
"minimatch": "^9.0.3"
|
|
44
|
-
"tslib": "^2.6.1"
|
|
43
|
+
"minimatch": "^9.0.3"
|
|
45
44
|
},
|
|
46
|
-
"gitHead": "
|
|
45
|
+
"gitHead": "78ab1adaa4e178b4187f6c5f135bb8fc0c8f4b9e"
|
|
47
46
|
}
|