appease 0.0.1 → 0.0.3
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 +21 -0
- package/README.md +276 -0
- package/dist/cli.d.ts +9 -0
- package/dist/cli.js +62 -0
- package/dist/cli.js.map +1 -0
- package/dist/core/analyze.d.ts +9 -0
- package/dist/core/analyze.js +30 -0
- package/dist/core/analyze.js.map +1 -0
- package/dist/core/audit.d.ts +14 -0
- package/dist/core/audit.js +46 -0
- package/dist/core/audit.js.map +1 -0
- package/dist/core/configs.d.ts +15 -0
- package/dist/core/configs.js +61 -0
- package/dist/core/configs.js.map +1 -0
- package/dist/core/editorconfig.d.ts +30 -0
- package/dist/core/editorconfig.js +99 -0
- package/dist/core/editorconfig.js.map +1 -0
- package/dist/core/gitattributes.d.ts +26 -0
- package/dist/core/gitattributes.js +70 -0
- package/dist/core/gitattributes.js.map +1 -0
- package/dist/core/merge.d.ts +39 -0
- package/dist/core/merge.js +98 -0
- package/dist/core/merge.js.map +1 -0
- package/dist/core/normalize.d.ts +13 -0
- package/dist/core/normalize.js +58 -0
- package/dist/core/normalize.js.map +1 -0
- package/dist/core/types.d.ts +102 -0
- package/dist/core/types.js +4 -0
- package/dist/core/types.js.map +1 -0
- package/dist/index.d.ts +17 -0
- package/dist/index.js +134 -0
- package/dist/index.js.map +1 -0
- package/dist/io/configs.d.ts +16 -0
- package/dist/io/configs.js +47 -0
- package/dist/io/configs.js.map +1 -0
- package/dist/io/files.d.ts +16 -0
- package/dist/io/files.js +43 -0
- package/dist/io/files.js.map +1 -0
- package/package.json +48 -1
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Emilio
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,276 @@
|
|
|
1
|
+
# appease
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
Make peace in this app.
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+

|
|
8
|
+
[](https://npmjs.org/package/appease)
|
|
9
|
+
[](https://npmjs.org/package/appease)
|
|
10
|
+
[](https://github.com/emilioplatzer/appease/actions/workflows/build-and-test.yml)
|
|
11
|
+
[](https://socket.dev/npm/package/appease)
|
|
12
|
+
[](https://github.com/emilioplatzer/appease/actions/workflows/qa-control.yml)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
language: 
|
|
16
|
+
also available in:
|
|
17
|
+
[](LEEME.md)
|
|
18
|
+
|
|
19
|
+
A tool to bring order, **once**, to the low-level format of a repository's files (line
|
|
20
|
+
endings, BOM, trailing spaces, final newline, indentation), and to leave the convention
|
|
21
|
+
configured so that keeping it afterwards is free and diffs stay clean and humanly reviewable.
|
|
22
|
+
|
|
23
|
+
The underlying idea: honoring "the exact format" on every commit is an effort (especially on
|
|
24
|
+
Windows, where line endings get mixed on their own). If instead you normalize once and pin
|
|
25
|
+
the convention with `.gitattributes` + `.editorconfig` + `.vscode/settings.json`, the cost
|
|
26
|
+
disappears.
|
|
27
|
+
|
|
28
|
+
The name is a play on words between **normalize** and **appease** (to bring to peace).
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
---
|
|
32
|
+
|
|
33
|
+
## What it normalizes, and where each decision lives
|
|
34
|
+
|
|
35
|
+
Each format "axis" is controlled from **a single configuration file**, depending on which
|
|
36
|
+
tool is capable of handling it. To answer "what happens to this file on a given axis?" you
|
|
37
|
+
never have to look at two files.
|
|
38
|
+
|
|
39
|
+
| Axis | Who handles it | Config file |
|
|
40
|
+
|---|---|---|
|
|
41
|
+
| Line ending (EOL) | Git | `.gitattributes` |
|
|
42
|
+
| BOM / charset | editor + appease | `.editorconfig` |
|
|
43
|
+
| Trailing spaces | editor + appease | `.editorconfig` |
|
|
44
|
+
| Final newline | editor + appease | `.editorconfig` |
|
|
45
|
+
| Indentation (convention) | editor | `.editorconfig` |
|
|
46
|
+
| Show whitespace on selection | VSC | `.vscode/settings.json` |
|
|
47
|
+
|
|
48
|
+
### Line ending (EOL) — handled by Git
|
|
49
|
+
|
|
50
|
+
It lives in `.gitattributes`, which is **per-repo and overrides Git's global configuration**,
|
|
51
|
+
so there's no need to touch anything in global Git (nor break other repos that rely on its
|
|
52
|
+
behavior).
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
```gitattributes
|
|
56
|
+
# Default: on commit, Git normalizes to LF in the repo (EOL disappears from diffs);
|
|
57
|
+
# on checkout, it delivers the OS-native EOL (CRLF on Windows, LF on Linux).
|
|
58
|
+
* text=auto
|
|
59
|
+
|
|
60
|
+
# One-off exceptions, one by one:
|
|
61
|
+
path/to/file.crlf text eol=crlf # always CRLF, everywhere
|
|
62
|
+
path/to/file.lf text eol=lf # always LF, everywhere
|
|
63
|
+
path/to/file.raw -text # byte for byte: leave everything as-is (commit and checkout)
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
Requirement (assumed on purpose): this default yields **native** EOL because each machine
|
|
67
|
+
resolves it via its `core.eol`, whose default value (when unset) is already `native`. appease
|
|
68
|
+
**does not touch Git's configuration** —neither global nor local— so native EOL remains an
|
|
69
|
+
environment requirement: it works as long as `core.eol` is not pinned to `lf`/`crlf`. You can
|
|
70
|
+
verify it, without changing anything, with `git config --get core.eol`.
|
|
71
|
+
|
|
72
|
+
When adopting or changing `.gitattributes`, already-committed files are not rewritten on
|
|
73
|
+
their own; a one-time pass is needed: `git add --renormalize .`.
|
|
74
|
+
|
|
75
|
+
### BOM / charset — handled by `.editorconfig`
|
|
76
|
+
|
|
77
|
+
Git does **not** know how to add or remove the BOM; that's why this axis lives in
|
|
78
|
+
`.editorconfig` (which VSC honors live) and appease applies it.
|
|
79
|
+
|
|
80
|
+
- Default: **UTF-8 without BOM** (the sane choice for JS/TS/JSON/web).
|
|
81
|
+
- `charset = utf-8-bom` → with a fixed BOM (PowerShell 5.1 with non-ASCII, CSV for Excel, etc.).
|
|
82
|
+
- `charset = unset` → leave the BOM as-is, don't touch it.
|
|
83
|
+
|
|
84
|
+
### Trailing spaces and final newline — `.editorconfig`
|
|
85
|
+
|
|
86
|
+
- `trim_trailing_whitespace = true` → trim (default). `= false` → leave them as-is
|
|
87
|
+
(typical in Markdown, where two trailing spaces are an intentional line break).
|
|
88
|
+
- `insert_final_newline = true` → guarantee exactly one (default). `= false` → leave
|
|
89
|
+
as-is.
|
|
90
|
+
|
|
91
|
+
### Indentation — a convention for the editor, **not** a rewrite
|
|
92
|
+
|
|
93
|
+
For now indentation is treated as a **convention**, not as a mass rewrite (converting
|
|
94
|
+
tab↔spaces is structural, it depends on the language —Makefile *requires* tabs, Go uses tabs—
|
|
95
|
+
and it's exactly the most invasive change).
|
|
96
|
+
|
|
97
|
+
- `.editorconfig`: `indent_style = space` (to stop adding more tabs), with registered
|
|
98
|
+
exceptions (`[Makefile] indent_style = tab`, Go, etc.).
|
|
99
|
+
- `.vscode/settings.json`: `editor.renderWhitespace = "selection"`, so that whitespace is
|
|
100
|
+
shown **only when selecting** text (tabs appear as little arrows on demand; with no
|
|
101
|
+
selection nothing shows). It's pinned in the repo to guarantee that behavior for the whole
|
|
102
|
+
team, without depending on each person's VSC default.
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
```json
|
|
106
|
+
{
|
|
107
|
+
"editor.renderWhitespace": "selection"
|
|
108
|
+
}
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
The actual conversion of tabs stays **out** of this workflow for now (see `--tabs-*`).
|
|
112
|
+
|
|
113
|
+
### Example `.editorconfig`
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
```editorconfig
|
|
117
|
+
root = true
|
|
118
|
+
|
|
119
|
+
# Default for everything
|
|
120
|
+
[*]
|
|
121
|
+
charset = utf-8 # no BOM
|
|
122
|
+
trim_trailing_whitespace = true
|
|
123
|
+
insert_final_newline = true
|
|
124
|
+
indent_style = space
|
|
125
|
+
|
|
126
|
+
# Markdown: the two trailing spaces are intentional
|
|
127
|
+
[*.md]
|
|
128
|
+
trim_trailing_whitespace = false
|
|
129
|
+
|
|
130
|
+
# Examples of one-off exceptions
|
|
131
|
+
[test/fixtures/excel/**]
|
|
132
|
+
charset = utf-8-bom
|
|
133
|
+
|
|
134
|
+
[test/fixtures/raw/**]
|
|
135
|
+
charset = unset
|
|
136
|
+
trim_trailing_whitespace = false
|
|
137
|
+
insert_final_newline = false
|
|
138
|
+
|
|
139
|
+
[Makefile]
|
|
140
|
+
indent_style = tab
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
---
|
|
144
|
+
|
|
145
|
+
## Modes
|
|
146
|
+
|
|
147
|
+
Every command prints at the end **which files it created or modified**.
|
|
148
|
+
|
|
149
|
+
Each command takes the directory to process as an optional positional argument
|
|
150
|
+
(`appease <command> [dir]`; defaults to the current directory).
|
|
151
|
+
|
|
152
|
+
| Command | Reads | Writes | Destructive |
|
|
153
|
+
|---|---|---|---|
|
|
154
|
+
| `audit` | existing configs (or the defaults it would propose) + the files | nothing, only **reports** what is out of spec | no |
|
|
155
|
+
| `add-config-defaults` | — | the configs with **pure defaults** (without looking at reality) | no (only creates config) |
|
|
156
|
+
| `adapt-configs` | configs + audit | creates or **adapts** the configs to reflect what was found | does not touch source code |
|
|
157
|
+
| `fix-format` | configs | **modifies the files** (BOM, trailing, newline; EOL via Git) honoring the configs | yes (Git reverts) |
|
|
158
|
+
|
|
159
|
+
### `audit`: report format
|
|
160
|
+
|
|
161
|
+
`audit` prints canonical JSON with **two** lists. The key point is that conforming files
|
|
162
|
+
**appear in neither**: only the ones that need attention and the ones that couldn't be
|
|
163
|
+
evaluated are listed. A clean repo yields both lists empty:
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
```json
|
|
167
|
+
{
|
|
168
|
+
"findings": [],
|
|
169
|
+
"notAnalyzed": []
|
|
170
|
+
}
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
- **`findings`**: **analyzed files that deviate** from their resolved config. Each entry
|
|
174
|
+
carries `path`, `deviations` (axes that differ from what the config asks for) and
|
|
175
|
+
`unresolved` (axes governed by an unrecognized config value: not evaluated, reported as-is).
|
|
176
|
+
- **`notAnalyzed`**: files that were **not analyzed**, with their `reason`
|
|
177
|
+
(`binary-extension`, `binary-content`, `gitattributes-notext`, `non-utf8`).
|
|
178
|
+
|
|
179
|
+
This format is **provisional**: today the output is the direct serialization of the
|
|
180
|
+
`AuditResult` type, meant to be easy to parse and test. It may grow if the value justifies it.
|
|
181
|
+
|
|
182
|
+
### `adapt-configs`: records every deviation as an exception
|
|
183
|
+
|
|
184
|
+
`adapt-configs` records **every** deviation it finds as an explicit exception, across all
|
|
185
|
+
axes equally (without classifying or guessing intent). This gives a safety invariant: **right
|
|
186
|
+
after `adapt-configs`, a `fix-format` changes nothing**, because the config describes
|
|
187
|
+
reality 100%. Only when you **prune** (delete) exceptions does `fix-format` touch *that and
|
|
188
|
+
only that*.
|
|
189
|
+
|
|
190
|
+
So, "everything is wrong and I want to fix it all at once" is solved by deleting the block of
|
|
191
|
+
exceptions: everything falls back to the default → `fix-format` rewrites whatever is needed.
|
|
192
|
+
|
|
193
|
+
Behavior details:
|
|
194
|
+
|
|
195
|
+
- A deviation can be **multi-axis** in a single file (CRLF + trailing + no final newline +
|
|
196
|
+
BOM). Its entry covers several properties; deleting it reverts that entire file to the
|
|
197
|
+
default. To keep a single property (rare) you edit the line instead of deleting it.
|
|
198
|
+
- Each exception lands in the **file that owns the axis**: EOL → `.gitattributes`, the rest →
|
|
199
|
+
`.editorconfig`. "Select all and delete" is per config file (two places).
|
|
200
|
+
|
|
201
|
+
### `--tabs-*` (out of scope for now)
|
|
202
|
+
|
|
203
|
+
Indentation conversion (tab→spaces or vice versa) is left as separate switches, to be defined
|
|
204
|
+
later, since it's structural, risky and language-dependent. In the meantime, tabs are fixed
|
|
205
|
+
by hand.
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
---
|
|
209
|
+
|
|
210
|
+
## Suggested workflow
|
|
211
|
+
|
|
212
|
+
0. *(optional)* `add-config-defaults` → **commit**. Versions the "north star" (the pure
|
|
213
|
+
norm), so that in step 1 deviations stand out in the diff against that norm.
|
|
214
|
+
1. `adapt-configs` → the `git diff` of the configs shows **every added exception = every
|
|
215
|
+
deviation**. That diff is the real report.
|
|
216
|
+
2. Review those exceptions: keep the ones that were on purpose, **delete** by hand the ones
|
|
217
|
+
that were junk (if almost everything is wrong, delete the whole block).
|
|
218
|
+
3. `fix-format` → normalizes everything no longer protected by an exception.
|
|
219
|
+
|
|
220
|
+
Since Git reverts anything, the destructive steps are safe to try.
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
---
|
|
224
|
+
|
|
225
|
+
## Architecture
|
|
226
|
+
|
|
227
|
+
Designed to **stand on its own** and to be integrable later as a dependency of another tool.
|
|
228
|
+
|
|
229
|
+
Text transformation is separated from orchestration (Git, file system, args), so the core is
|
|
230
|
+
pure and testable.
|
|
231
|
+
|
|
232
|
+
### Pure function (with its tests)
|
|
233
|
+
|
|
234
|
+
The heart is a side-effect-free function: it receives a file's content (already decoded) and
|
|
235
|
+
the options resolved for that file, and returns the normalized content plus a report of what
|
|
236
|
+
changed. It touches neither disk, nor Git, nor arguments.
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
```
|
|
240
|
+
normalizeText(content: string, options: Options): { content: string; report: Report }
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
- `Options`: BOM (remove | add | keep), trailing (trim | keep), final newline (ensure | keep).
|
|
244
|
+
(EOL is Git's responsibility, not this function's.)
|
|
245
|
+
- `Report`: what was modified (so as not to fail silently).
|
|
246
|
+
|
|
247
|
+
The audit also has its pure core: given a chunk of text, it detects its current state (EOL
|
|
248
|
+
crlf/lf/mixed, BOM yes/no, trailing yes/no, final newline, indentation tabs/spaces/mixed).
|
|
249
|
+
|
|
250
|
+
Test cases: CRLF↔LF, mixed EOL, trailing, missing/excess final newline, BOM present/absent,
|
|
251
|
+
empty file, and **idempotence** (running twice changes nothing).
|
|
252
|
+
|
|
253
|
+
Strongly typed, `strict`, no `any`. Errors are handled, not ignored.
|
|
254
|
+
|
|
255
|
+
|
|
256
|
+
### `cli.ts`
|
|
257
|
+
|
|
258
|
+
Maps the commands (`audit`, `add-config-defaults`, `adapt-configs`, `fix-format`) to
|
|
259
|
+
TS calls and orchestrates the effects:
|
|
260
|
+
|
|
261
|
+
1. Discovers files (`git ls-files`), skips binaries and the ones marked `-text`.
|
|
262
|
+
2. Reads `.gitattributes` and `.editorconfig` to resolve the per-file options.
|
|
263
|
+
3. Depending on the command: only reports, generates/adapts configs, or reads each file, calls
|
|
264
|
+
the pure function and rewrites if it changed.
|
|
265
|
+
4. Prints the summary of created/modified files.
|
|
266
|
+
|
|
267
|
+
|
|
268
|
+
---
|
|
269
|
+
|
|
270
|
+
## To be defined at implementation time
|
|
271
|
+
|
|
272
|
+
- Default value for `indent_size` (probably detected per project/language).
|
|
273
|
+
- Exact format of the `audit` report (provisional today, documented above).
|
|
274
|
+
- Binary detection and handling of files in encodings other than UTF-8.
|
|
275
|
+
- Concrete `--tabs-*` switches.
|
|
276
|
+
|
package/dist/cli.d.ts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import type { RunOptions } from "./core/types.js";
|
|
3
|
+
/**
|
|
4
|
+
* Parse argv into resolved RunOptions. cleye is the single source of truth: each RunMode is a
|
|
5
|
+
* subcommand (`appease <command> [dir]`), so the modes are mutually exclusive by construction and
|
|
6
|
+
* each gets its own `--help`. cleye prints usage and exits on unknown flags and on `--help`.
|
|
7
|
+
*/
|
|
8
|
+
export declare function parseArgs(argv?: string[]): RunOptions;
|
|
9
|
+
export declare function main(argv: string[]): Promise<number>;
|
package/dist/cli.js
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// CLI entry point. Maps switches to the public API and orchestrates the effects.
|
|
3
|
+
import { resolve } from "node:path";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
5
|
+
import { cli, command } from "cleye";
|
|
6
|
+
import { runAppease } from "./index.js";
|
|
7
|
+
/** `--dry-run`, shared by the commands that write to disk (not by `audit`, which never writes). */
|
|
8
|
+
const dryRunFlag = {
|
|
9
|
+
dryRun: { type: Boolean, description: "Simulate writes; report what would change but touch nothing" },
|
|
10
|
+
};
|
|
11
|
+
/**
|
|
12
|
+
* Parse argv into resolved RunOptions. cleye is the single source of truth: each RunMode is a
|
|
13
|
+
* subcommand (`appease <command> [dir]`), so the modes are mutually exclusive by construction and
|
|
14
|
+
* each gets its own `--help`. cleye prints usage and exits on unknown flags and on `--help`.
|
|
15
|
+
*/
|
|
16
|
+
export function parseArgs(argv) {
|
|
17
|
+
const parsed = cli({
|
|
18
|
+
name: "appease",
|
|
19
|
+
help: { description: "Normalize a repo's text files to its .editorconfig / .gitattributes. One command is required." },
|
|
20
|
+
commands: [
|
|
21
|
+
command({ name: "audit", parameters: ["[dir]"], strictFlags: true, help: { description: "Report deviations only (no writes)" } }),
|
|
22
|
+
command({ name: "add-config-defaults", parameters: ["[dir]"], strictFlags: true, flags: dryRunFlag, help: { description: "Write pure-default configs (only the ones that do not exist yet)" } }),
|
|
23
|
+
command({ name: "adapt-configs", parameters: ["[dir]"], strictFlags: true, flags: dryRunFlag, help: { description: "Write/adapt configs to reflect the repo's current reality" } }),
|
|
24
|
+
command({ name: "fix-format", parameters: ["[dir]"], strictFlags: true, flags: dryRunFlag, help: { description: "Normalize files (BOM / EOL / trailing whitespace / final newline)" } }),
|
|
25
|
+
],
|
|
26
|
+
}, undefined, argv);
|
|
27
|
+
if (parsed.command === undefined) {
|
|
28
|
+
parsed.showHelp();
|
|
29
|
+
process.stderr.write("\nSpecify a command: audit | add-config-defaults | adapt-configs | fix-format\n");
|
|
30
|
+
process.exit(1);
|
|
31
|
+
}
|
|
32
|
+
const dir = parsed._.dir;
|
|
33
|
+
return {
|
|
34
|
+
mode: parsed.command,
|
|
35
|
+
cwd: dir !== undefined ? resolve(dir) : process.cwd(),
|
|
36
|
+
dryRun: parsed.command === "audit" ? false : parsed.flags.dryRun ?? false,
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
/** Drop empty `unresolved` arrays from the audit JSON (an empty list carries no information). */
|
|
40
|
+
function omitEmptyUnresolved(key, value) {
|
|
41
|
+
return key === "unresolved" && Array.isArray(value) && value.length === 0 ? undefined : value;
|
|
42
|
+
}
|
|
43
|
+
export async function main(argv) {
|
|
44
|
+
const report = await runAppease(parseArgs(argv));
|
|
45
|
+
if (report.audit !== undefined)
|
|
46
|
+
process.stdout.write(`${JSON.stringify(report.audit, omitEmptyUnresolved, 2)}\n`);
|
|
47
|
+
for (const path of report.created)
|
|
48
|
+
process.stdout.write(`${report.dryRun ? "would create" : "created"}: ${path}\n`);
|
|
49
|
+
for (const path of report.modified)
|
|
50
|
+
process.stdout.write(`${report.dryRun ? "would modify" : "modified"}: ${path}\n`);
|
|
51
|
+
for (const path of report.unchanged)
|
|
52
|
+
process.stdout.write(`unchanged: ${path}\n`);
|
|
53
|
+
return 0;
|
|
54
|
+
}
|
|
55
|
+
// Only run when invoked directly (not when imported by tests).
|
|
56
|
+
if (process.argv[1] === fileURLToPath(import.meta.url)) {
|
|
57
|
+
main(process.argv.slice(2)).then((code) => process.exit(code), (err) => {
|
|
58
|
+
process.stderr.write(`${err instanceof Error ? err.stack ?? err.message : String(err)}\n`);
|
|
59
|
+
process.exit(1);
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
//# sourceMappingURL=cli.js.map
|
package/dist/cli.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { FormatReport } from "./types.js";
|
|
2
|
+
/**
|
|
3
|
+
* Audit core: detect the current low-level format state of a single file's
|
|
4
|
+
* already-decoded text. Pure and side-effect free (no disk, no Git, no args).
|
|
5
|
+
*
|
|
6
|
+
* Assumes the decoder kept a leading BOM (U+FEFF) in `content` if the file had
|
|
7
|
+
* one (the orchestration decodes raw, without stripping it).
|
|
8
|
+
*/
|
|
9
|
+
export declare function analyzeContent(content: string): FormatReport;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Audit core: detect the current low-level format state of a single file's
|
|
3
|
+
* already-decoded text. Pure and side-effect free (no disk, no Git, no args).
|
|
4
|
+
*
|
|
5
|
+
* Assumes the decoder kept a leading BOM (U+FEFF) in `content` if the file had
|
|
6
|
+
* one (the orchestration decodes raw, without stripping it).
|
|
7
|
+
*/
|
|
8
|
+
export function analyzeContent(content) {
|
|
9
|
+
const empty = content === "";
|
|
10
|
+
const hasBom = content.charCodeAt(0) === 0xfeff;
|
|
11
|
+
// Count CRLF, CR and LF separately. Each CRLF contains one CR and one LF, so
|
|
12
|
+
// the lone-CR / lone-LF counts come out by subtraction.
|
|
13
|
+
const crlfCount = (content.match(/\r\n/g) ?? []).length;
|
|
14
|
+
const crCount = (content.match(/\r/g) ?? []).length;
|
|
15
|
+
const lfCount = (content.match(/\n/g) ?? []).length;
|
|
16
|
+
const hasCrlf = crlfCount > 0;
|
|
17
|
+
const hasCr = crCount - crlfCount > 0;
|
|
18
|
+
const hasLf = lfCount - crlfCount > 0;
|
|
19
|
+
// Horizontal whitespace right before a line break or at the end of the file.
|
|
20
|
+
const hasTrailingSpaces = /[ \t]+(?:\r?\n|$)/.test(content);
|
|
21
|
+
const finalNewline = detectFinalNewline(content);
|
|
22
|
+
return { empty, hasBom, hasCrlf, hasLf, hasCr, hasTrailingSpaces, finalNewline };
|
|
23
|
+
}
|
|
24
|
+
/** Classify the trailing run of newlines (LF / CRLF only). */
|
|
25
|
+
function detectFinalNewline(content) {
|
|
26
|
+
if (!/(?:\r\n|\n)$/.test(content))
|
|
27
|
+
return "missing";
|
|
28
|
+
return /(?:\r\n|\n){2}$/.test(content) ? "multiple" : "present";
|
|
29
|
+
}
|
|
30
|
+
//# sourceMappingURL=analyze.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"analyze.js","sourceRoot":"","sources":["../../src/core/analyze.ts"],"names":[],"mappings":"AAEA;;;;;;GAMG;AACH,MAAM,UAAU,cAAc,CAAC,OAAe;IAC5C,MAAM,KAAK,GAAG,OAAO,KAAK,EAAE,CAAC;IAC7B,MAAM,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC,CAAC,CAAC,KAAK,MAAM,CAAC;IAEhD,6EAA6E;IAC7E,wDAAwD;IACxD,MAAM,SAAS,GAAG,CAAC,OAAO,CAAC,KAAK,CAAC,OAAO,CAAC,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC;IACxD,MAAM,OAAO,GAAG,CAAC,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC;IACpD,MAAM,OAAO,GAAG,CAAC,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC;IACpD,MAAM,OAAO,GAAG,SAAS,GAAG,CAAC,CAAC;IAC9B,MAAM,KAAK,GAAG,OAAO,GAAG,SAAS,GAAG,CAAC,CAAC;IACtC,MAAM,KAAK,GAAG,OAAO,GAAG,SAAS,GAAG,CAAC,CAAC;IAEtC,6EAA6E;IAC7E,MAAM,iBAAiB,GAAG,mBAAmB,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;IAE5D,MAAM,YAAY,GAAG,kBAAkB,CAAC,OAAO,CAAC,CAAC;IAEjD,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,iBAAiB,EAAE,YAAY,EAAE,CAAC;AACnF,CAAC;AAED,8DAA8D;AAC9D,SAAS,kBAAkB,CAAC,OAAe;IACzC,IAAI,CAAC,cAAc,CAAC,IAAI,CAAC,OAAO,CAAC;QAAE,OAAO,SAAS,CAAC;IACpD,OAAO,iBAAiB,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,SAAS,CAAC;AAClE,CAAC"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { FileAudit, FormatReport, ProjectConfig } from "./types.js";
|
|
2
|
+
/**
|
|
3
|
+
* Evaluate a collection of per-file analyses against the resolved project
|
|
4
|
+
* config. Pure. Returns one `FileAudit` per file that has any finding
|
|
5
|
+
* (deviation or unresolved axis); clean files are omitted.
|
|
6
|
+
*
|
|
7
|
+
* `nativeEol` is this machine's native line ending (CRLF on Windows, LF
|
|
8
|
+
* elsewhere); it is how `eol=auto` is evaluated, since under `text=auto` the
|
|
9
|
+
* working copy is expected to be native. The pure core receives it as input.
|
|
10
|
+
*/
|
|
11
|
+
export declare function audit(reports: {
|
|
12
|
+
path: string;
|
|
13
|
+
report: FormatReport;
|
|
14
|
+
}[], config: ProjectConfig, nativeEol: "lf" | "crlf"): FileAudit[];
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Evaluate a collection of per-file analyses against the resolved project
|
|
3
|
+
* config. Pure. Returns one `FileAudit` per file that has any finding
|
|
4
|
+
* (deviation or unresolved axis); clean files are omitted.
|
|
5
|
+
*
|
|
6
|
+
* `nativeEol` is this machine's native line ending (CRLF on Windows, LF
|
|
7
|
+
* elsewhere); it is how `eol=auto` is evaluated, since under `text=auto` the
|
|
8
|
+
* working copy is expected to be native. The pure core receives it as input.
|
|
9
|
+
*/
|
|
10
|
+
export function audit(reports, config, nativeEol) {
|
|
11
|
+
const files = [];
|
|
12
|
+
for (const { path, report } of reports) {
|
|
13
|
+
const cfg = config.resolve(path);
|
|
14
|
+
const deviations = fileDeviations(report, cfg, nativeEol);
|
|
15
|
+
if (deviations.length > 0 || cfg.unresolved.length > 0)
|
|
16
|
+
files.push({ path, deviations, unresolved: cfg.unresolved });
|
|
17
|
+
}
|
|
18
|
+
return files;
|
|
19
|
+
}
|
|
20
|
+
/** Axes where `report` differs from `cfg`, skipping axes the config could not resolve (case 2). */
|
|
21
|
+
function fileDeviations(report, cfg, nativeEol) {
|
|
22
|
+
const deviations = [];
|
|
23
|
+
const governs = (axis) => !cfg.unresolved.includes(axis);
|
|
24
|
+
if (governs("bom")) {
|
|
25
|
+
if (cfg.bom === "add" && !report.hasBom)
|
|
26
|
+
deviations.push("bom");
|
|
27
|
+
else if (cfg.bom === "remove" && report.hasBom)
|
|
28
|
+
deviations.push("bom");
|
|
29
|
+
}
|
|
30
|
+
if (governs("trailing") && cfg.trailing === "trim" && report.hasTrailingSpaces) {
|
|
31
|
+
deviations.push("trailing");
|
|
32
|
+
}
|
|
33
|
+
if (governs("finalNewline") && cfg.finalNewline === "ensure" && report.finalNewline !== "present") {
|
|
34
|
+
deviations.push("finalNewline");
|
|
35
|
+
}
|
|
36
|
+
if (governs("eol")) {
|
|
37
|
+
const target = cfg.eol === "auto" ? nativeEol : cfg.eol;
|
|
38
|
+
if (target === "lf" && (report.hasCrlf || report.hasCr))
|
|
39
|
+
deviations.push("eol");
|
|
40
|
+
else if (target === "crlf" && (report.hasLf || report.hasCr))
|
|
41
|
+
deviations.push("eol");
|
|
42
|
+
// "binary" -> EOL is not evaluated
|
|
43
|
+
}
|
|
44
|
+
return deviations;
|
|
45
|
+
}
|
|
46
|
+
//# sourceMappingURL=audit.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"audit.js","sourceRoot":"","sources":["../../src/core/audit.ts"],"names":[],"mappings":"AAEA;;;;;;;;GAQG;AACH,MAAM,UAAU,KAAK,CACnB,OAAiD,EACjD,MAAqB,EACrB,SAAwB;IAExB,MAAM,KAAK,GAAgB,EAAE,CAAC;IAC9B,KAAK,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,OAAO,EAAE,CAAC;QACvC,MAAM,GAAG,GAAG,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;QACjC,MAAM,UAAU,GAAG,cAAc,CAAC,MAAM,EAAE,GAAG,EAAE,SAAS,CAAC,CAAC;QAC1D,IAAI,UAAU,CAAC,MAAM,GAAG,CAAC,IAAI,GAAG,CAAC,UAAU,CAAC,MAAM,GAAG,CAAC;YAAE,KAAK,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,UAAU,EAAE,UAAU,EAAE,GAAG,CAAC,UAAU,EAAE,CAAC,CAAC;IACvH,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED,mGAAmG;AACnG,SAAS,cAAc,CAAC,MAAoB,EAAE,GAAuB,EAAE,SAAwB;IAC7F,MAAM,UAAU,GAAoB,EAAE,CAAC;IACvC,MAAM,OAAO,GAAG,CAAC,IAAmB,EAAW,EAAE,CAAC,CAAC,GAAG,CAAC,UAAU,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;IAEjF,IAAI,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;QACnB,IAAI,GAAG,CAAC,GAAG,KAAK,KAAK,IAAI,CAAC,MAAM,CAAC,MAAM;YAAE,UAAU,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;aAC3D,IAAI,GAAG,CAAC,GAAG,KAAK,QAAQ,IAAI,MAAM,CAAC,MAAM;YAAE,UAAU,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IACzE,CAAC;IACD,IAAI,OAAO,CAAC,UAAU,CAAC,IAAI,GAAG,CAAC,QAAQ,KAAK,MAAM,IAAI,MAAM,CAAC,iBAAiB,EAAE,CAAC;QAC/E,UAAU,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;IAC9B,CAAC;IACD,IAAI,OAAO,CAAC,cAAc,CAAC,IAAI,GAAG,CAAC,YAAY,KAAK,QAAQ,IAAI,MAAM,CAAC,YAAY,KAAK,SAAS,EAAE,CAAC;QAClG,UAAU,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC;IAClC,CAAC;IACD,IAAI,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;QACnB,MAAM,MAAM,GAAG,GAAG,CAAC,GAAG,KAAK,MAAM,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC;QACxD,IAAI,MAAM,KAAK,IAAI,IAAI,CAAC,MAAM,CAAC,OAAO,IAAI,MAAM,CAAC,KAAK,CAAC;YAAE,UAAU,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;aAC3E,IAAI,MAAM,KAAK,MAAM,IAAI,CAAC,MAAM,CAAC,KAAK,IAAI,MAAM,CAAC,KAAK,CAAC;YAAE,UAAU,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QACrF,mCAAmC;IACrC,CAAC;IACD,OAAO,UAAU,CAAC;AACpB,CAAC"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { ProjectConfig, RawConfigs } from "./types.js";
|
|
2
|
+
/**
|
|
3
|
+
* Interpret the raw contents of the three config sources into a resolver for
|
|
4
|
+
* per-file options. Pure: takes the already-read contents, touches no disk.
|
|
5
|
+
*
|
|
6
|
+
* Each axis is owned by exactly one source (see LEEME.md): EOL -> .gitattributes,
|
|
7
|
+
* BOM/charset/trailing/final-newline -> .editorconfig, renderWhitespace ->
|
|
8
|
+
* .vscode/settings.json. Only `.editorconfig` and `.gitattributes` drive
|
|
9
|
+
* `resolve`; `.vscode/settings.json` only counts towards `present`.
|
|
10
|
+
*/
|
|
11
|
+
export declare function interpretConfigs(raw: RawConfigs): ProjectConfig;
|
|
12
|
+
/** Pure, idempotent defaults for `.editorconfig` (no inspection of the repo's reality). */
|
|
13
|
+
export declare function defaultEditorconfig(): string;
|
|
14
|
+
/** Pure, idempotent defaults for `.gitattributes` (`* text=auto`, ...). */
|
|
15
|
+
export declare function defaultGitattributes(): string;
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { parseEditorconfig, resolveEditorconfig } from "./editorconfig.js";
|
|
2
|
+
import { parseGitattributes, resolveGitEol } from "./gitattributes.js";
|
|
3
|
+
/**
|
|
4
|
+
* Interpret the raw contents of the three config sources into a resolver for
|
|
5
|
+
* per-file options. Pure: takes the already-read contents, touches no disk.
|
|
6
|
+
*
|
|
7
|
+
* Each axis is owned by exactly one source (see LEEME.md): EOL -> .gitattributes,
|
|
8
|
+
* BOM/charset/trailing/final-newline -> .editorconfig, renderWhitespace ->
|
|
9
|
+
* .vscode/settings.json. Only `.editorconfig` and `.gitattributes` drive
|
|
10
|
+
* `resolve`; `.vscode/settings.json` only counts towards `present`.
|
|
11
|
+
*/
|
|
12
|
+
export function interpretConfigs(raw) {
|
|
13
|
+
const editorconfig = raw.editorconfig !== null ? parseEditorconfig(raw.editorconfig) : { root: false, sections: [] };
|
|
14
|
+
const gitattributes = raw.gitattributes !== null ? parseGitattributes(raw.gitattributes) : { rules: [] };
|
|
15
|
+
return {
|
|
16
|
+
present: {
|
|
17
|
+
editorconfig: raw.editorconfig !== null,
|
|
18
|
+
gitattributes: raw.gitattributes !== null,
|
|
19
|
+
vscodeSettings: raw.vscodeSettings !== null,
|
|
20
|
+
},
|
|
21
|
+
resolve: (path) => resolveFile(editorconfig, gitattributes, path),
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
/** Combine the per-source resolutions into the file's resolved config. */
|
|
25
|
+
function resolveFile(editorconfig, gitattributes, path) {
|
|
26
|
+
const ec = resolveEditorconfig(editorconfig, path);
|
|
27
|
+
const gitEol = resolveGitEol(gitattributes, path);
|
|
28
|
+
const unresolved = [...ec.unresolved];
|
|
29
|
+
if (gitEol === "unresolved")
|
|
30
|
+
unresolved.push("eol");
|
|
31
|
+
return {
|
|
32
|
+
bom: ec.charset === "utf-8-bom" ? "add" : ec.charset === "utf-8" ? "remove" : "keep",
|
|
33
|
+
eol: gitEol === "unresolved" ? "auto" : gitEol,
|
|
34
|
+
trailing: ec.trailing,
|
|
35
|
+
finalNewline: ec.finalNewline,
|
|
36
|
+
unresolved,
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
/** Pure, idempotent defaults for `.editorconfig` (no inspection of the repo's reality). */
|
|
40
|
+
export function defaultEditorconfig() {
|
|
41
|
+
return [
|
|
42
|
+
"root = true",
|
|
43
|
+
"",
|
|
44
|
+
"# Defaults for every file",
|
|
45
|
+
"[*]",
|
|
46
|
+
"charset = utf-8",
|
|
47
|
+
"trim_trailing_whitespace = true",
|
|
48
|
+
"insert_final_newline = true",
|
|
49
|
+
"indent_style = space",
|
|
50
|
+
"",
|
|
51
|
+
].join("\n");
|
|
52
|
+
}
|
|
53
|
+
/** Pure, idempotent defaults for `.gitattributes` (`* text=auto`, ...). */
|
|
54
|
+
export function defaultGitattributes() {
|
|
55
|
+
return [
|
|
56
|
+
"# Normalize to LF in the repo on commit; check out with the OS-native EOL.",
|
|
57
|
+
"* text=auto",
|
|
58
|
+
"",
|
|
59
|
+
].join("\n");
|
|
60
|
+
}
|
|
61
|
+
//# sourceMappingURL=configs.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"configs.js","sourceRoot":"","sources":["../../src/core/configs.ts"],"names":[],"mappings":"AACA,OAAO,EAAqB,iBAAiB,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AAC9F,OAAO,EAAsB,kBAAkB,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAE3F;;;;;;;;GAQG;AACH,MAAM,UAAU,gBAAgB,CAAC,GAAe;IAC9C,MAAM,YAAY,GAAiB,GAAG,CAAC,YAAY,KAAK,IAAI,CAAC,CAAC,CAAC,iBAAiB,CAAC,GAAG,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,QAAQ,EAAE,EAAE,EAAE,CAAC;IACnI,MAAM,aAAa,GAAkB,GAAG,CAAC,aAAa,KAAK,IAAI,CAAC,CAAC,CAAC,kBAAkB,CAAC,GAAG,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,EAAE,EAAE,CAAC;IACxH,OAAO;QACL,OAAO,EAAE;YACP,YAAY,EAAE,GAAG,CAAC,YAAY,KAAK,IAAI;YACvC,aAAa,EAAE,GAAG,CAAC,aAAa,KAAK,IAAI;YACzC,cAAc,EAAE,GAAG,CAAC,cAAc,KAAK,IAAI;SAC5C;QACD,OAAO,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,WAAW,CAAC,YAAY,EAAE,aAAa,EAAE,IAAI,CAAC;KAClE,CAAC;AACJ,CAAC;AAED,0EAA0E;AAC1E,SAAS,WAAW,CAAC,YAA0B,EAAE,aAA4B,EAAE,IAAY;IACzF,MAAM,EAAE,GAAG,mBAAmB,CAAC,YAAY,EAAE,IAAI,CAAC,CAAC;IACnD,MAAM,MAAM,GAAG,aAAa,CAAC,aAAa,EAAE,IAAI,CAAC,CAAC;IAClD,MAAM,UAAU,GAAoB,CAAC,GAAG,EAAE,CAAC,UAAU,CAAC,CAAC;IACvD,IAAI,MAAM,KAAK,YAAY;QAAE,UAAU,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IACpD,OAAO;QACL,GAAG,EAAE,EAAE,CAAC,OAAO,KAAK,WAAW,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC,OAAO,KAAK,OAAO,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,MAAM;QACpF,GAAG,EAAE,MAAM,KAAK,YAAY,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM;QAC9C,QAAQ,EAAE,EAAE,CAAC,QAAQ;QACrB,YAAY,EAAE,EAAE,CAAC,YAAY;QAC7B,UAAU;KACX,CAAC;AACJ,CAAC;AAED,2FAA2F;AAC3F,MAAM,UAAU,mBAAmB;IACjC,OAAO;QACL,aAAa;QACb,EAAE;QACF,2BAA2B;QAC3B,KAAK;QACL,iBAAiB;QACjB,iCAAiC;QACjC,6BAA6B;QAC7B,sBAAsB;QACtB,EAAE;KACH,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AACf,CAAC;AAED,2EAA2E;AAC3E,MAAM,UAAU,oBAAoB;IAClC,OAAO;QACL,4EAA4E;QAC5E,aAAa;QACb,EAAE;KACH,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AACf,CAAC"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/** Axes an `.editorconfig` can govern for us; an axis here means "skip + report" (case 2). */
|
|
2
|
+
export type EditorconfigAxis = "bom" | "trailing" | "finalNewline";
|
|
3
|
+
/** Resolved `.editorconfig` stance for a path (only the axes we manage). */
|
|
4
|
+
export interface EditorconfigResolution {
|
|
5
|
+
/** `keep` = `unset`, `false`-like, or not specified. */
|
|
6
|
+
charset: "utf-8" | "utf-8-bom" | "keep";
|
|
7
|
+
trailing: "trim" | "keep";
|
|
8
|
+
finalNewline: "ensure" | "keep";
|
|
9
|
+
/** Axes governed by a recognized key with an unrecognized value (case 2). */
|
|
10
|
+
unresolved: EditorconfigAxis[];
|
|
11
|
+
}
|
|
12
|
+
interface Section {
|
|
13
|
+
matches: (path: string) => boolean;
|
|
14
|
+
charset: "utf-8" | "utf-8-bom" | "unset" | "unrecognized" | undefined;
|
|
15
|
+
trim: boolean | "unset" | "unrecognized" | undefined;
|
|
16
|
+
finalNewline: boolean | "unset" | "unrecognized" | undefined;
|
|
17
|
+
}
|
|
18
|
+
export interface Editorconfig {
|
|
19
|
+
root: boolean;
|
|
20
|
+
sections: Section[];
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Parse `.editorconfig` content into ordered sections. Pure.
|
|
24
|
+
* Throws on a structurally malformed line (case 3); properties we do not manage
|
|
25
|
+
* (indent_*, max_line_length, ...) are ignored (case 1).
|
|
26
|
+
*/
|
|
27
|
+
export declare function parseEditorconfig(content: string): Editorconfig;
|
|
28
|
+
/** Resolve the effective stance for `path` (last matching section wins per property). */
|
|
29
|
+
export declare function resolveEditorconfig(ec: Editorconfig, path: string): EditorconfigResolution;
|
|
30
|
+
export {};
|