load-oxfmt-config 0.3.0 → 0.4.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/README.md +18 -3
- package/dist/index.d.mts +9 -0
- package/dist/index.mjs +104 -6
- package/package.json +9 -9
package/README.md
CHANGED
|
@@ -111,6 +111,19 @@ const config = await loadOxfmtConfig({
|
|
|
111
111
|
})
|
|
112
112
|
```
|
|
113
113
|
|
|
114
|
+
### Override `.editorconfig` Search Directory
|
|
115
|
+
|
|
116
|
+
```ts
|
|
117
|
+
import { loadOxfmtConfig } from 'load-oxfmt-config'
|
|
118
|
+
|
|
119
|
+
// Search .editorconfig from a custom directory instead of the config file's directory
|
|
120
|
+
const config = await loadOxfmtConfig({
|
|
121
|
+
editorconfig: {
|
|
122
|
+
cwd: '/path/to/editorconfig-dir',
|
|
123
|
+
},
|
|
124
|
+
})
|
|
125
|
+
```
|
|
126
|
+
|
|
114
127
|
### Disable Caching
|
|
115
128
|
|
|
116
129
|
```ts
|
|
@@ -203,9 +216,10 @@ Control how `.editorconfig` files are read and merged:
|
|
|
203
216
|
- **`false`** — Disable `.editorconfig` reading entirely.
|
|
204
217
|
- **`EditorconfigOption`** — Enable with additional settings:
|
|
205
218
|
|
|
206
|
-
| Property | Type | Default
|
|
207
|
-
| --------- | --------- |
|
|
208
|
-
| `onlyCwd` | `boolean` | `false`
|
|
219
|
+
| Property | Type | Default | Description |
|
|
220
|
+
| --------- | --------- | ----------- | --------------------------------------------------------------------------------------------------------------------- |
|
|
221
|
+
| `onlyCwd` | `boolean` | `false` | When `true`, only look for `.editorconfig` in `cwd` itself — no upward traversal. |
|
|
222
|
+
| `cwd` | `string` | `undefined` | Override the directory from which `.editorconfig` resolution starts, instead of the config file's directory or `cwd`. |
|
|
209
223
|
|
|
210
224
|
## Config File Discovery
|
|
211
225
|
|
|
@@ -273,6 +287,7 @@ The loader reads the nearest `.editorconfig` file and maps the subset of fields
|
|
|
273
287
|
- `indent_size` / `tab_width` → `tabWidth`
|
|
274
288
|
- `max_line_length` → `printWidth`
|
|
275
289
|
- `insert_final_newline` → `insertFinalNewline`
|
|
290
|
+
- `quote_type` → `singleQuote`
|
|
276
291
|
|
|
277
292
|
Glob sections such as `[*.ts]` are converted into returned `overrides` entries.
|
|
278
293
|
|
package/dist/index.d.mts
CHANGED
|
@@ -12,6 +12,15 @@ interface EditorconfigOption {
|
|
|
12
12
|
* @default false
|
|
13
13
|
*/
|
|
14
14
|
onlyCwd?: boolean;
|
|
15
|
+
/**
|
|
16
|
+
* Override the directory from which `.editorconfig` resolution starts.
|
|
17
|
+
* When set, editorconfig is searched from this directory instead of from
|
|
18
|
+
* the config file's directory (or the top-level `cwd`).
|
|
19
|
+
*
|
|
20
|
+
* This is useful when the oxfmt config path is pre-resolved and you still
|
|
21
|
+
* want editorconfig to be resolved relative to each file's directory.
|
|
22
|
+
*/
|
|
23
|
+
cwd?: string;
|
|
15
24
|
}
|
|
16
25
|
/**
|
|
17
26
|
* Format option override for a single matching rule
|
package/dist/index.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { readFile, stat } from "node:fs/promises";
|
|
2
2
|
import { dirname, isAbsolute, join, relative } from "node:path";
|
|
3
3
|
import process from "node:process";
|
|
4
|
-
import { interopDefault } from "@ntnyq/utils";
|
|
4
|
+
import { interopDefault, isBoolean, isNumber, isObject } from "@ntnyq/utils";
|
|
5
5
|
import { parse } from "jsonc-parser";
|
|
6
6
|
import { parseBuffer } from "editorconfig";
|
|
7
7
|
//#region src/constants.ts
|
|
@@ -24,23 +24,57 @@ const EDITORCONFIG_FILE = ".editorconfig";
|
|
|
24
24
|
const EDITORCONFIG_GLOBAL_SECTION_NAMES = ["*", "**"];
|
|
25
25
|
//#endregion
|
|
26
26
|
//#region src/editorconfig.ts
|
|
27
|
+
/**
|
|
28
|
+
* Builds the cache key used for resolved EditorConfig lookups.
|
|
29
|
+
*
|
|
30
|
+
* @param resolveKey - The base resolution key for the current lookup.
|
|
31
|
+
* @returns The namespaced cache key for EditorConfig resolution.
|
|
32
|
+
*/
|
|
27
33
|
function getEditorconfigResolveCacheKey(resolveKey) {
|
|
28
34
|
return `editorconfig::${resolveKey}`;
|
|
29
35
|
}
|
|
36
|
+
/**
|
|
37
|
+
* Determines the directory that should be used to search for a nearby .editorconfig file.
|
|
38
|
+
*
|
|
39
|
+
* @param cwd - The current working directory for the lookup.
|
|
40
|
+
* @param configPath - An optional config path used to anchor the search.
|
|
41
|
+
* @returns The directory where EditorConfig discovery should begin.
|
|
42
|
+
*/
|
|
30
43
|
function getEditorconfigSearchDir(cwd, configPath) {
|
|
31
44
|
if (!configPath) return cwd;
|
|
32
45
|
return dirname(isAbsolute(configPath) ? configPath : join(cwd, configPath));
|
|
33
46
|
}
|
|
47
|
+
/**
|
|
48
|
+
* Merges root-level EditorConfig options with oxfmt config options.
|
|
49
|
+
*
|
|
50
|
+
* @param oxfmtConfig - The explicit oxfmt options.
|
|
51
|
+
* @param editorconfigRootOptions - Root options derived from .editorconfig.
|
|
52
|
+
* @returns A single root options object where oxfmt takes precedence.
|
|
53
|
+
*/
|
|
34
54
|
function mergeRootOptions(oxfmtConfig, editorconfigRootOptions) {
|
|
35
55
|
return {
|
|
36
56
|
...editorconfigRootOptions,
|
|
37
57
|
...oxfmtConfig
|
|
38
58
|
};
|
|
39
59
|
}
|
|
60
|
+
/**
|
|
61
|
+
* Merges EditorConfig-derived overrides with explicit oxfmt overrides.
|
|
62
|
+
*
|
|
63
|
+
* @param oxfmtOverrides - Overrides declared in the oxfmt config.
|
|
64
|
+
* @param editorconfigOverrides - Overrides derived from .editorconfig sections.
|
|
65
|
+
* @returns The merged overrides array, or undefined when no overrides exist.
|
|
66
|
+
*/
|
|
40
67
|
function mergeOverrides(oxfmtOverrides, editorconfigOverrides) {
|
|
41
68
|
const mergedOverrides = [...editorconfigOverrides, ...oxfmtOverrides || []];
|
|
42
69
|
return mergedOverrides.length > 0 ? mergedOverrides : void 0;
|
|
43
70
|
}
|
|
71
|
+
/**
|
|
72
|
+
* Resolves the nearest .editorconfig file starting from a directory and optionally walking upward.
|
|
73
|
+
*
|
|
74
|
+
* @param startDir - The directory where the search starts.
|
|
75
|
+
* @param onlyCwd - When true, only checks the starting directory.
|
|
76
|
+
* @returns The resolved .editorconfig path, or undefined when none is found.
|
|
77
|
+
*/
|
|
44
78
|
async function resolveEditorconfigPath(startDir, onlyCwd = false) {
|
|
45
79
|
let currentDir = startDir;
|
|
46
80
|
while (true) {
|
|
@@ -54,19 +88,59 @@ async function resolveEditorconfigPath(startDir, onlyCwd = false) {
|
|
|
54
88
|
currentDir = parentDir;
|
|
55
89
|
}
|
|
56
90
|
}
|
|
91
|
+
/**
|
|
92
|
+
* Normalizes a file path to POSIX separators for glob compatibility.
|
|
93
|
+
*
|
|
94
|
+
* @param path - The path to normalize.
|
|
95
|
+
* @returns The path with forward slashes.
|
|
96
|
+
*/
|
|
57
97
|
function toPosixPath(path) {
|
|
58
98
|
return path.replaceAll("\\", "/");
|
|
59
99
|
}
|
|
100
|
+
/**
|
|
101
|
+
* Checks whether an EditorConfig section should be treated as a global section.
|
|
102
|
+
*
|
|
103
|
+
* @param sectionName - The section name parsed from .editorconfig.
|
|
104
|
+
* @returns True when the section applies globally.
|
|
105
|
+
*/
|
|
60
106
|
function isEditorconfigGlobalSection(sectionName) {
|
|
61
107
|
return Boolean(sectionName && EDITORCONFIG_GLOBAL_SECTION_NAMES.includes(sectionName));
|
|
62
108
|
}
|
|
109
|
+
/**
|
|
110
|
+
* Parses an EditorConfig boolean string.
|
|
111
|
+
*
|
|
112
|
+
* @param value - The raw EditorConfig value.
|
|
113
|
+
* @returns The parsed boolean, or undefined when the value is unsupported.
|
|
114
|
+
*/
|
|
63
115
|
function parseEditorconfigBoolean(value) {
|
|
64
116
|
if (value === "true") return true;
|
|
65
117
|
if (value === "false") return false;
|
|
66
118
|
}
|
|
119
|
+
/**
|
|
120
|
+
* Parses the end-of-line style supported by oxfmt.
|
|
121
|
+
*
|
|
122
|
+
* @param value - The raw EditorConfig end_of_line value.
|
|
123
|
+
* @returns The normalized line ending value, or undefined when unsupported.
|
|
124
|
+
*/
|
|
67
125
|
function parseEditorconfigEndOfLine(value) {
|
|
68
126
|
if (value === "lf" || value === "crlf" || value === "cr") return value;
|
|
69
127
|
}
|
|
128
|
+
/**
|
|
129
|
+
* Parses the single quote preference from an EditorConfig section.
|
|
130
|
+
*
|
|
131
|
+
* @param value - The raw EditorConfig value for single quote preference.
|
|
132
|
+
* @returns True if single quotes are preferred, false if double quotes are preferred, or undefined when auto.
|
|
133
|
+
*/
|
|
134
|
+
function parseEditorconfigQuoteType(value) {
|
|
135
|
+
if (value === "single") return true;
|
|
136
|
+
if (value === "double") return false;
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Resolves the effective tab width from an EditorConfig section.
|
|
140
|
+
*
|
|
141
|
+
* @param section - The parsed EditorConfig section body.
|
|
142
|
+
* @returns The resolved tab width, or undefined when no numeric value is available.
|
|
143
|
+
*/
|
|
70
144
|
function parseEditorconfigTabWidth(section) {
|
|
71
145
|
const indentSize = section["indent_size"];
|
|
72
146
|
const tabWidth = section["tab_width"];
|
|
@@ -79,27 +153,50 @@ function parseEditorconfigTabWidth(section) {
|
|
|
79
153
|
if (Number.isFinite(parsedTabWidth)) return parsedTabWidth;
|
|
80
154
|
}
|
|
81
155
|
}
|
|
156
|
+
/**
|
|
157
|
+
* Maps a parsed EditorConfig section to the subset of oxfmt formatter options it supports.
|
|
158
|
+
*
|
|
159
|
+
* @param section - The parsed EditorConfig section body.
|
|
160
|
+
* @returns Formatter options derived from the section.
|
|
161
|
+
*/
|
|
82
162
|
function mapEditorconfigSectionToOptions(section) {
|
|
83
163
|
const options = {};
|
|
84
164
|
const endOfLine = parseEditorconfigEndOfLine(section["end_of_line"]);
|
|
85
165
|
if (endOfLine) options.endOfLine = endOfLine;
|
|
86
166
|
if (section["indent_style"] === "tab") options.useTabs = true;
|
|
87
167
|
else if (section["indent_style"] === "space") options.useTabs = false;
|
|
168
|
+
const singleQuote = parseEditorconfigQuoteType(section["quote_type"]);
|
|
169
|
+
if (isBoolean(singleQuote)) options.singleQuote = singleQuote;
|
|
88
170
|
const tabWidth = parseEditorconfigTabWidth(section);
|
|
89
|
-
if (
|
|
171
|
+
if (isNumber(tabWidth)) options.tabWidth = tabWidth;
|
|
90
172
|
if (section["max_line_length"] && section["max_line_length"] !== "unset") {
|
|
91
173
|
const parsedPrintWidth = Number(section["max_line_length"]);
|
|
92
174
|
if (Number.isFinite(parsedPrintWidth)) options.printWidth = parsedPrintWidth;
|
|
93
175
|
}
|
|
94
176
|
const insertFinalNewline = parseEditorconfigBoolean(section["insert_final_newline"]);
|
|
95
|
-
if (
|
|
177
|
+
if (isBoolean(insertFinalNewline)) options.insertFinalNewline = insertFinalNewline;
|
|
96
178
|
return options;
|
|
97
179
|
}
|
|
180
|
+
/**
|
|
181
|
+
* Rebases an EditorConfig section pattern from the config directory to the target anchor directory.
|
|
182
|
+
*
|
|
183
|
+
* @param pattern - The original EditorConfig section pattern.
|
|
184
|
+
* @param editorconfigDir - The directory containing the .editorconfig file.
|
|
185
|
+
* @param anchorDir - The directory used as the override pattern base.
|
|
186
|
+
* @returns The rebased glob pattern.
|
|
187
|
+
*/
|
|
98
188
|
function rebaseEditorconfigPattern(pattern, editorconfigDir, anchorDir) {
|
|
99
189
|
const relativePrefix = toPosixPath(relative(anchorDir, editorconfigDir));
|
|
100
190
|
if (!relativePrefix || relativePrefix === ".") return pattern;
|
|
101
191
|
return `${relativePrefix}/${pattern.replace(/^\//, "")}`;
|
|
102
192
|
}
|
|
193
|
+
/**
|
|
194
|
+
* Reads a .editorconfig file and converts it into root options and override entries.
|
|
195
|
+
*
|
|
196
|
+
* @param editorconfigPath - The absolute path to the .editorconfig file.
|
|
197
|
+
* @param anchorDir - The directory used to rebase section patterns.
|
|
198
|
+
* @returns Parsed EditorConfig data ready to merge into oxfmt config.
|
|
199
|
+
*/
|
|
103
200
|
async function readEditorconfigFromFile(editorconfigPath, anchorDir) {
|
|
104
201
|
const parsedSections = parseBuffer(await readFile(editorconfigPath));
|
|
105
202
|
const editorconfigDir = dirname(editorconfigPath);
|
|
@@ -223,10 +320,11 @@ async function loadOxfmtConfig(options = {}) {
|
|
|
223
320
|
const cwd = options.cwd || process.cwd();
|
|
224
321
|
const editorconfig = options.editorconfig ?? true;
|
|
225
322
|
const useEditorconfig = editorconfig !== false;
|
|
226
|
-
const onlyCwd = useEditorconfig &&
|
|
323
|
+
const onlyCwd = useEditorconfig && isObject(editorconfig) ? editorconfig.onlyCwd ?? false : false;
|
|
324
|
+
const editorconfigCwd = useEditorconfig && isObject(editorconfig) ? editorconfig.cwd : void 0;
|
|
227
325
|
const resolveKey = getResolveCacheKey(cwd, options.configPath);
|
|
228
|
-
const editorconfigSearchDir = getEditorconfigSearchDir(cwd, options.configPath);
|
|
229
|
-
const editorconfigResolveKey = getEditorconfigResolveCacheKey(resolveKey);
|
|
326
|
+
const editorconfigSearchDir = editorconfigCwd || getEditorconfigSearchDir(cwd, options.configPath);
|
|
327
|
+
const editorconfigResolveKey = editorconfigCwd ? getEditorconfigResolveCacheKey(`${editorconfigCwd}::${options.configPath || ""}`) : getEditorconfigResolveCacheKey(resolveKey);
|
|
230
328
|
const resolvedPath = useCache ? await cachePromise(resolveCache, resolveKey, () => resolveOxfmtrcPath(cwd, options.configPath)) : await resolveOxfmtrcPath(cwd, options.configPath);
|
|
231
329
|
const editorconfigPath = useEditorconfig ? await (useCache ? cachePromise(resolveCache, editorconfigResolveKey, () => resolveEditorconfigPath(editorconfigSearchDir, onlyCwd)) : resolveEditorconfigPath(editorconfigSearchDir, onlyCwd)) : void 0;
|
|
232
330
|
const anchorDir = dirname(resolvedPath || editorconfigPath || cwd);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "load-oxfmt-config",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "Load oxfmt config files and merge supported .editorconfig options.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"editorconfig",
|
|
@@ -39,23 +39,23 @@
|
|
|
39
39
|
"access": "public"
|
|
40
40
|
},
|
|
41
41
|
"dependencies": {
|
|
42
|
-
"@ntnyq/utils": "^0.
|
|
42
|
+
"@ntnyq/utils": "^0.12.0",
|
|
43
43
|
"editorconfig": "^3.0.2",
|
|
44
44
|
"jiti": "^2.6.1",
|
|
45
45
|
"jsonc-parser": "^3.3.1"
|
|
46
46
|
},
|
|
47
47
|
"devDependencies": {
|
|
48
48
|
"@ntnyq/tsconfig": "^3.1.0",
|
|
49
|
-
"@types/node": "^25.
|
|
50
|
-
"@typescript/native-preview": "^7.0.0-dev.
|
|
49
|
+
"@types/node": "^25.6.0",
|
|
50
|
+
"@typescript/native-preview": "^7.0.0-dev.20260413.1",
|
|
51
51
|
"bumpp": "^11.0.1",
|
|
52
52
|
"husky": "^9.1.7",
|
|
53
|
-
"nano-staged": "^0.
|
|
53
|
+
"nano-staged": "^1.0.2",
|
|
54
54
|
"npm-run-all2": "^8.0.4",
|
|
55
|
-
"oxfmt": "^0.
|
|
56
|
-
"oxlint": "^1.
|
|
57
|
-
"tsdown": "^0.21.
|
|
58
|
-
"vitest": "^4.1.
|
|
55
|
+
"oxfmt": "^0.45.0",
|
|
56
|
+
"oxlint": "^1.60.0",
|
|
57
|
+
"tsdown": "^0.21.8",
|
|
58
|
+
"vitest": "^4.1.4"
|
|
59
59
|
},
|
|
60
60
|
"peerDependencies": {
|
|
61
61
|
"oxfmt": ">=0.41.0"
|