@preventive/compare 1.2.1 → 1.2.2
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/compare.d.ts +14 -8
- package/package.json +1 -1
- package/pretty.d.ts +4 -3
- package/src/changes.js +22 -14
- package/src/erasure.js +7 -1
- package/src/pretty.js +104 -20
- package/src/types.node.js +3 -3
package/compare.d.ts
CHANGED
|
@@ -42,12 +42,15 @@ export type Status = 'added' | 'deleted' | 'modified' | 'renamed' | 'type-change
|
|
|
42
42
|
// retained module load or `export {}` runs the same on both sides only within
|
|
43
43
|
// one module format -- an `export {}` emits a marker transpiled to CommonJS, a
|
|
44
44
|
// module load runs as one format and throws as the other -- so where the runtime
|
|
45
|
-
// loads a module
|
|
46
|
-
//
|
|
47
|
-
//
|
|
45
|
+
// loads a module, carries a bare `export {}`, or default-exports an identifier a
|
|
46
|
+
// rename whose extensions fix different formats (a `.d.mts` to a `.d.cts`, or a
|
|
47
|
+
// `.d.ts` to or from either) is not stamped, while a rename between two of the same
|
|
48
|
+
// extension (two `.d.ts`, two `.d.mts`, two `.d.cts`) is; a hashbang, a directive or a pragma runs the same either
|
|
48
49
|
// way, so a runtime of only those cancels across formats. A modification keeps its
|
|
49
|
-
// path, hence its format, and
|
|
50
|
-
// runtime
|
|
50
|
+
// path, hence its format, and a deleted file matches only an empty runtime; an
|
|
51
|
+
// added one an empty runtime or a single bare `export {}`, as nothing loaded it
|
|
52
|
+
// before and what now does runs nothing of that alone, as a module or not. A
|
|
53
|
+
// rename is otherwise taken by its matching
|
|
51
54
|
// source, as any file's is here: a relative load may resolve to another module
|
|
52
55
|
// once the file moves, but that is shown as a rename of its own, not traced. A
|
|
53
56
|
// modification of a package-resolved `.d.ts` is taken to keep its format, though
|
|
@@ -217,12 +220,15 @@ export type TypeOnlyReason = 'unsupported' | 'unparsed' | 'code' | 'disagree'
|
|
|
217
220
|
// function's signature, `import type` and `export type`, a namespace of types
|
|
218
221
|
// alone -- and those it leaves to run: a module load that binds from it
|
|
219
222
|
// (`import`, `export *`, `export { a } from` a module, `import { type A }` whose
|
|
220
|
-
// inline `type` loads it all the same)
|
|
223
|
+
// inline `type` loads it all the same), a bare `export {}`, and `export default E`
|
|
224
|
+
// on a bare identifier (the idiom a declaration file's default export takes,
|
|
225
|
+
// `declare const _d: T; export default _d`). `export { type A }`
|
|
221
226
|
// is code, not a run statement: it is `export {}` only once stripping takes its
|
|
222
227
|
// specifiers out, not as the source has it; nor is `export as namespace N`, left
|
|
223
228
|
// as invalid JavaScript that throws when imported, nor a side-effect load that
|
|
224
229
|
// binds nothing -- a bare `import`, an empty `import {}`, `export {}` with a
|
|
225
|
-
// source -- which loads a module for no type
|
|
230
|
+
// source -- which loads a module for no type, nor an `export default` of anything
|
|
231
|
+
// but a bare identifier (a value expression, an object, a class body);
|
|
226
232
|
// - Node's stripTypeScriptTypes erases, to the character, what oxc finds erased
|
|
227
233
|
// and leaves nothing it does not, but comments, the hashbang and those;
|
|
228
234
|
// - what it leaves, the run statements set aside, read token by token apart from
|
|
@@ -244,7 +250,7 @@ export type TypeOnlyReason = 'unsupported' | 'unparsed' | 'code' | 'disagree'
|
|
|
244
250
|
// point. A triple-slash directive counts only in the prologue,
|
|
245
251
|
// before the first statement, where TypeScript honours it; below one it is prose, so moving
|
|
246
252
|
// it out of the prologue drops it from `runtime`, as removing it does. `loads` is whether any
|
|
247
|
-
// run statement is a module load
|
|
253
|
+
// run statement is a module load, a bare `export {}`, or an `export default` on an identifier: the runtime that runs as one module
|
|
248
254
|
// format and throws, or emits a different marker, as the other -- a hashbang, directive or
|
|
249
255
|
// pragma does not. A change is types only where both sides prove and leave the same `runtime`:
|
|
250
256
|
// it then touches erased types and ordinary comments alone. Where `loads`, a rename across
|
package/package.json
CHANGED
package/pretty.d.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
// `text` formatted by oxfmt as a file with `extension`, changing what is written
|
|
2
|
-
// no more than layout needs (keys keep their quotes,
|
|
3
|
-
//
|
|
4
|
-
//
|
|
2
|
+
// no more than layout needs (keys keep their quotes, names their escapes, a comment
|
|
3
|
+
// its place after an operator; no trailing commas or arrow parentheses are added,
|
|
4
|
+
// template literals stay), the copy used only where checks of oxfmt's output, apart
|
|
5
|
+
// from oxfmt, find it changes the layout alone:
|
|
5
6
|
// - a script's (`js`, `mjs`, `cjs`, `jsx`, `ts`, `mts`, `cts`, `tsx`) syntax tree as
|
|
6
7
|
// oxc parses it, its comments and its text;
|
|
7
8
|
// - a style sheet's (`css`) tree as css-tree, an optional peer, parses it, but for
|
package/src/changes.js
CHANGED
|
@@ -54,9 +54,13 @@ const DECLARATION = /\.d\.(?:[^./]+\.)?[cm]?ts$/u
|
|
|
54
54
|
// a change across them is not types only.
|
|
55
55
|
const fixedModule = (path) => (path.endsWith('.mts') ? 'module' : path.endsWith('.cts') ? 'commonjs' : null)
|
|
56
56
|
|
|
57
|
+
// A runtime of a single bare `export {}`, any trailing `;`, as proveTypeOnly spells one: its blanks as the
|
|
58
|
+
// source has them, a comment within it a single blank.
|
|
59
|
+
const EXPORT_EMPTY = /^export\s*\{\s*\}\s*;?$/u
|
|
60
|
+
|
|
57
61
|
// A file's proof: that it holds types alone but for its `runtime` -- a hashbang, the module loads and
|
|
58
62
|
// `export {}` stripping leaves -- which a change must leave byte for byte untouched to be types only.
|
|
59
|
-
// `loads` is whether that runtime holds a module load
|
|
63
|
+
// `loads` is whether that runtime holds a module load, `export {}`, or an `export default` on an identifier, the part that runs differently per
|
|
60
64
|
// module format; a hashbang, a directive or a pragma does not, so a runtime of only those is format-free. A
|
|
61
65
|
// side not there has none. Not proven where the proof throws, as no check should.
|
|
62
66
|
function proven(file) {
|
|
@@ -71,28 +75,32 @@ function proven(file) {
|
|
|
71
75
|
|
|
72
76
|
// Stamped where each side there is a declaration file proven to hold types alone and the two leave the
|
|
73
77
|
// same runtime -- a change touching only erased types (and comments). A mode changing is a change of its
|
|
74
|
-
// own. Where that runtime loads a module
|
|
75
|
-
// within one module format, so a rename
|
|
76
|
-
//
|
|
78
|
+
// own. Where that runtime loads a module, carries a bare `export {}`, or default-exports an identifier it runs
|
|
79
|
+
// the same on both sides only within one module format, so a rename cancels it only between two extensions
|
|
80
|
+
// that fix the same format -- two `.d.ts` (both package-resolved), two `.d.mts`, or two `.d.cts`; a rename
|
|
81
|
+
// whose extensions differ (a `.d.mts` to a `.d.cts`, or a `.d.ts` to or from either) does not qualify, since
|
|
82
|
+
// an extension pins a format where a package-resolved `.d.ts` leaves it open. A hashbang, a directive or a pragma runs the same either way,
|
|
77
83
|
// so a runtime of only those cancels across formats. A modification keeps its path, hence its format, and
|
|
78
|
-
//
|
|
84
|
+
// a deleted file matches only an empty runtime; an added one an empty runtime or a single bare `export {}`
|
|
85
|
+
// (EXPORT_EMPTY). Nothing loaded an added file before, so only what else the diff changes can, shown as a
|
|
86
|
+
// change of its own, and that loading runs nothing of a bare `export {}` alone: it runs nothing as a module
|
|
87
|
+
// and throws on it as CommonJS, whatever the extension. A module load beside it would run another module.
|
|
79
88
|
// A rename is taken by its matching source, as any file's is here: a relative load in the runtime may
|
|
80
89
|
// resolve to another module once the file moves, but that is the rename's doing, shown as a rename of its
|
|
81
|
-
// own, not traced. Known limitation: a
|
|
82
|
-
//
|
|
83
|
-
//
|
|
84
|
-
//
|
|
90
|
+
// own, not traced. Known limitation: a package-resolved `.d.ts` is taken to keep its format across a
|
|
91
|
+
// modification (same path) and to share one across a rename to another `.d.ts`, since format is read from
|
|
92
|
+
// the extension, not resolved from a package. The governing `package.json` could differ -- edited between
|
|
93
|
+
// the two trees, or a different one at the rename's destination -- and flip the format; that flip rides the
|
|
94
|
+
// `package.json` change or the move, each seen in the diff on its own, and is not traced here.
|
|
85
95
|
|
|
86
96
|
function stamped(change) {
|
|
87
97
|
const sides = [change.old, change.new].filter((file) => file !== null)
|
|
88
98
|
if (!sides.every((file) => file.type === 'file' && DECLARATION.test(file.path))) return change
|
|
89
99
|
if (change.old && change.new && change.old.mode !== change.new.mode) return change
|
|
90
100
|
const fresh = proven(change.new), old = proven(change.old)
|
|
91
|
-
if (!old.proven || !fresh.proven
|
|
92
|
-
if (old.
|
|
93
|
-
|
|
94
|
-
if (format === null || format !== fixedModule(change.new.path)) return change
|
|
95
|
-
}
|
|
101
|
+
if (!old.proven || !fresh.proven) return change
|
|
102
|
+
if (old.runtime !== fresh.runtime && !(change.old === null && EXPORT_EMPTY.test(fresh.runtime))) return change
|
|
103
|
+
if (old.loads && change.old && change.new && change.old.path !== change.new.path && fixedModule(change.old.path) !== fixedModule(change.new.path)) return change
|
|
96
104
|
return { ...change, typeOnly: true }
|
|
97
105
|
}
|
|
98
106
|
|
package/src/erasure.js
CHANGED
|
@@ -72,7 +72,13 @@ export function strippedAs(statement) {
|
|
|
72
72
|
// load; `export { a } from` a module re-exports a binding. A bare `export {}`, no source, is a marker.
|
|
73
73
|
if (statement.source) return bindingless(statement) ? CODE : IMPORT
|
|
74
74
|
return statement.specifiers.length === 0 ? INERT : CODE
|
|
75
|
-
case 'ExportDefaultDeclaration':
|
|
75
|
+
case 'ExportDefaultDeclaration':
|
|
76
|
+
if (statement.declaration.type === 'TSInterfaceDeclaration' || statement.declaration.type === 'TSDeclareFunction') return ERASED
|
|
77
|
+
// `export default E`, a bare identifier, re-exports a binding by name: a run statement that runs as an
|
|
78
|
+
// ES module and throws as CommonJS, like `export {}`, so it is inert here, not code -- it is the idiom a
|
|
79
|
+
// declaration file's default export takes (`declare const _d: T; export default _d`). Any other default,
|
|
80
|
+
// a value expression or a class body among it, is code a declaration file has no reason to hold.
|
|
81
|
+
return statement.declaration.type === 'Identifier' ? INERT : CODE
|
|
76
82
|
// `export as namespace N` is left verbatim, invalid as JavaScript, so importing the file throws.
|
|
77
83
|
case 'TSNamespaceExportDeclaration': return CODE
|
|
78
84
|
default: return erased(statement) ? ERASED : CODE
|
package/src/pretty.js
CHANGED
|
@@ -29,11 +29,11 @@ function numberSpelling(text) {
|
|
|
29
29
|
|
|
30
30
|
const sorted = (flags) => [...flags].toSorted().join('')
|
|
31
31
|
// A script's text but for what the formatter may respell, beside the syntax tree compared after:
|
|
32
|
-
// blanks, parentheses, semicolons, quotes
|
|
33
|
-
//
|
|
32
|
+
// blanks, parentheses, semicolons, quotes and numbers, and a comma before a closing bracket, past any
|
|
33
|
+
// comments (`import { a, b, }`). A regular expression's flags it may reorder are sorted where the
|
|
34
|
+
// syntax tree has them (respelled).
|
|
34
35
|
const scriptForm = (text) => text.replaceAll(NUMBER, numberSpelling).replaceAll(/,(?=(?:\s|\/\/[^\n]*|\/\*[^]*?\*\/)*[\])}>])/gu, '')
|
|
35
36
|
.replaceAll(/\\(?=["'])|[\s();]/gu, '').replaceAll("'", '"')
|
|
36
|
-
.replaceAll(/(?<=\/)[dgimsuvy]{2,}(?![\p{L}\p{N}_$])/gu, sorted)
|
|
37
37
|
|
|
38
38
|
// JSON has no point without a digit either side: such a number is kept as written, or a copy would read as
|
|
39
39
|
// valid, as would one with other blanks than JSON's four, which stay.
|
|
@@ -84,32 +84,48 @@ function sameTree(a, b) {
|
|
|
84
84
|
return true
|
|
85
85
|
}
|
|
86
86
|
|
|
87
|
-
//
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
// and a union from the mark leading it, so each is taken where it is, in whatever context.
|
|
91
|
-
function typed(text, program) {
|
|
92
|
-
const edits = [], work = [program]
|
|
87
|
+
// Each node of a syntax tree given to `each`.
|
|
88
|
+
function visit(program, each) {
|
|
89
|
+
const work = [program]
|
|
93
90
|
while (work.length > 0) {
|
|
94
91
|
const node = work.pop()
|
|
95
92
|
// Pushed one by one: a spread of a file's statements could pass more arguments than a call takes.
|
|
96
93
|
if (Array.isArray(node)) for (const item of node) work.push(item)
|
|
97
94
|
else if (node !== null && typeof node === 'object') {
|
|
98
|
-
|
|
99
|
-
for (const member of members) if (text[member.end - 1] === ',') edits.push([member.end - 1, ';'])
|
|
100
|
-
const mark = node.type === 'TSUnionType' ? '|' : node.type === 'TSIntersectionType' ? '&' : null
|
|
101
|
-
if (mark && text[node.start] === mark && node.types[0]?.start > node.start) edits.push([node.start, ''])
|
|
95
|
+
each(node)
|
|
102
96
|
for (const key of Object.keys(node)) if (!POSITION.has(key) && typeof node[key] === 'object') work.push(node[key])
|
|
103
97
|
}
|
|
104
98
|
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// `text` with each of `edits`, `[from, to, put]`, none overlapping another.
|
|
102
|
+
function edited(text, edits) {
|
|
105
103
|
let from = 0, out = ''
|
|
106
|
-
for (const [
|
|
107
|
-
out += text.slice(from,
|
|
108
|
-
from =
|
|
104
|
+
for (const [start, end, put] of edits.toSorted(([x], [y]) => x - y)) {
|
|
105
|
+
out += text.slice(from, start) + put
|
|
106
|
+
from = end
|
|
109
107
|
}
|
|
110
108
|
return out + text.slice(from)
|
|
111
109
|
}
|
|
112
110
|
|
|
111
|
+
// A script as the formatter may write it, read off its syntax tree: each regular expression's flags
|
|
112
|
+
// sorted (`/x/yu` as `/x/uy`), and where it has `types`, a member's separator `;` where it was `,`
|
|
113
|
+
// (`{ a: string, b: number }`), and no `|` or `&` before a union's or an intersection's first type
|
|
114
|
+
// (`type P =\n | A\n | B`, `[K in\n | A\n | B]`). oxc reads a member with the separator ending it
|
|
115
|
+
// and a union from the mark leading it, so each is taken where it is, in whatever context.
|
|
116
|
+
function respelled(text, program, types) {
|
|
117
|
+
const edits = []
|
|
118
|
+
visit(program, (node) => {
|
|
119
|
+
if (node.regex) edits.push([node.end - node.regex.flags.length, node.end, sorted(node.regex.flags)])
|
|
120
|
+
if (!types) return
|
|
121
|
+
const members = node.type === 'TSTypeLiteral' ? node.members : node.type === 'TSInterfaceBody' ? node.body : []
|
|
122
|
+
for (const member of members) if (text[member.end - 1] === ',') edits.push([member.end - 1, member.end, ';'])
|
|
123
|
+
const mark = node.type === 'TSUnionType' ? '|' : node.type === 'TSIntersectionType' ? '&' : null
|
|
124
|
+
if (mark && text[node.start] === mark && node.types[0]?.start > node.start) edits.push([node.start, node.start + 1, ''])
|
|
125
|
+
})
|
|
126
|
+
return edited(text, edits)
|
|
127
|
+
}
|
|
128
|
+
|
|
113
129
|
// A safeguard on oxfmt's own output, not a comparison of any two texts: exported
|
|
114
130
|
// for tests alone, not by the package. CSS is read by `csstree` where given.
|
|
115
131
|
export function sameCode(text, formatted, extension = 'js', csstree = null) {
|
|
@@ -118,10 +134,76 @@ export function sameCode(text, formatted, extension = 'js', csstree = null) {
|
|
|
118
134
|
if (!SCRIPTS.has(extension)) return false
|
|
119
135
|
const [before, after] = [parsed(text, extension), parsed(formatted, extension)]
|
|
120
136
|
if (before === null || after === null) return false
|
|
121
|
-
const form = (code, { program }) => scriptForm(/^[cm]?tsx?$/u.test(extension)
|
|
137
|
+
const form = (code, { program }) => scriptForm(respelled(code, program, /^[cm]?tsx?$/u.test(extension)))
|
|
122
138
|
return form(text, before) === form(formatted, after) && sameList(comments(before), comments(after)) && sameTree(before.program, after.program)
|
|
123
139
|
}
|
|
124
140
|
|
|
141
|
+
// A name as written from where it starts, escapes and all.
|
|
142
|
+
const NAME = /(?:\\u\{[\dA-Fa-f]+\}|\\u[\dA-Fa-f]{4}|[$\p{ID_Continue}\u{200C}\u{200D}])+/uy
|
|
143
|
+
// What oxfmt may write after a block comment the file has after it: an operator, `,`, `?` or `:`.
|
|
144
|
+
const MOVED = /[\t ]*([!%&*+,\-/:<=>?^|~]+|in(?:stanceof)?(?![$\p{ID_Continue}]))/uy
|
|
145
|
+
const MARK = /[!%&*+,\-/:<=>?^|~]/u
|
|
146
|
+
const NAME_PART = /[$\p{ID_Continue}]/u
|
|
147
|
+
|
|
148
|
+
// Each name of a syntax tree, `[where it starts, what it reads, as written]`, in order: a shorthand
|
|
149
|
+
// property's key and value, one name, once.
|
|
150
|
+
function names(text, program) {
|
|
151
|
+
const found = new Map()
|
|
152
|
+
visit(program, (node) => {
|
|
153
|
+
if (node.type !== 'Identifier' && node.type !== 'PrivateIdentifier') return
|
|
154
|
+
const at = node.type === 'Identifier' ? node.start : node.start + 1
|
|
155
|
+
NAME.lastIndex = at
|
|
156
|
+
found.set(at, [at, node.name, NAME.exec(text)?.[0] ?? ''])
|
|
157
|
+
})
|
|
158
|
+
return [...found.values()].toSorted(([x], [y]) => x - y)
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// Where the blanks on its line before `at` start.
|
|
162
|
+
function blanksFrom(text, at) {
|
|
163
|
+
let from = at
|
|
164
|
+
while (from > 0 && (text[from - 1] === ' ' || text[from - 1] === '\t')) from--
|
|
165
|
+
return from
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// The copy as the file writes what oxfmt writes otherwise, where that changes no more than its layout,
|
|
169
|
+
// before it is checked as any copy is:
|
|
170
|
+
// - a name's escapes, which oxfmt reads (`\u{61}` as `a`): a copy shows no character the file only
|
|
171
|
+
// escapes, one unseen, a joiner in a name say, or one like another;
|
|
172
|
+
// - a block comment after an operator, a `,`, `?` or `:`, which oxfmt moves before it where the comment
|
|
173
|
+
// ends its line (`a &&/* c */\nb` as `a /* c */ && b`): where a comment is may matter to what reads
|
|
174
|
+
// it, a bundler a `#__PURE__` mark say, and a reader sees it where the file has it.
|
|
175
|
+
// Names and comments are paired in order, as the check finds them alike or refuses the copy.
|
|
176
|
+
function asWritten(text, code, extension) {
|
|
177
|
+
if (!SCRIPTS.has(extension) || !/\\u|\/\*/u.test(text)) return code
|
|
178
|
+
const [before, after] = [parsed(text, extension), parsed(code, extension)]
|
|
179
|
+
if (before === null || after === null) return code
|
|
180
|
+
const edits = []
|
|
181
|
+
const [olds, news] = [names(text, before.program), names(code, after.program)]
|
|
182
|
+
if (olds.length === news.length) {
|
|
183
|
+
olds.forEach(([, name, written], i) => {
|
|
184
|
+
const [at, read, spelled] = news[i]
|
|
185
|
+
if (written !== spelled && written.includes('\\') && name === read) edits.push([at, at + spelled.length, written])
|
|
186
|
+
})
|
|
187
|
+
}
|
|
188
|
+
const [was, now] = [before.comments, after.comments]
|
|
189
|
+
for (let i = 0; was.length === now.length && i < now.length; i++) {
|
|
190
|
+
// A run of comments on one line moves as one.
|
|
191
|
+
const first = i
|
|
192
|
+
while (i + 1 < now.length && /^[\t ]*$/u.test(code.slice(now[i].end, now[i + 1].start))) i++
|
|
193
|
+
MOVED.lastIndex = now[i].end
|
|
194
|
+
const from = blanksFrom(code, now[first].start), moved = MOVED.exec(code)
|
|
195
|
+
if (moved === null || from === 0 || code[from - 1] === '\n') continue
|
|
196
|
+
const [, mark] = moved
|
|
197
|
+
// One the copy has before the comment too stayed, the one after another (`a + /* c */ +b`).
|
|
198
|
+
if (code.startsWith(mark, from - mark.length)) continue
|
|
199
|
+
const at = blanksFrom(text, was[first].start) - mark.length
|
|
200
|
+
if (at >= 0 && text.startsWith(mark, at) && !(mark.startsWith('i') ? NAME_PART : MARK).test(text[at - 1] ?? '')) {
|
|
201
|
+
edits.push([from, now[first].start, `${mark === ',' ? '' : ' '}${mark} `], [now[i].end, now[i].end + moved[0].length, ''])
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
return edited(code, edits)
|
|
205
|
+
}
|
|
206
|
+
|
|
125
207
|
// A script's block comments over more than one line, a license's say, each end their line: what oxfmt
|
|
126
208
|
// leaves after one on its last line (`*/ class A {`) goes to the next, indented as the comment's first
|
|
127
209
|
// line is. A comment holding a line break ends a line to the parser already (`return /*\n*/ 1` returns),
|
|
@@ -151,10 +233,12 @@ export async function format(text, extension) {
|
|
|
151
233
|
const oxfmt = await loadFormatter()
|
|
152
234
|
const result = await oxfmt(`pretty.${extension}`, text, PRETTY_OPTIONS).catch(() => null)
|
|
153
235
|
if (result === null || result.errors.length > 0) return { error: 'unformattable' }
|
|
154
|
-
//
|
|
236
|
+
// oxfmt's copy as it is, or where the check refuses it, as the file writes what oxfmt respelled: a
|
|
237
|
+
// copy the check takes respelled nothing so, as its text would differ. A check that fails in itself,
|
|
238
|
+
// its stack say, refuses the copy as one that finds it changed would.
|
|
239
|
+
const checked = (code) => (sameCode(text, code, extension, csstree) ? { code } : null)
|
|
155
240
|
try {
|
|
156
|
-
|
|
157
|
-
return sameCode(text, code, extension, csstree) ? { code } : { error: 'pretty-mismatch' }
|
|
241
|
+
return checked(breakAfterComments(result.code, extension)) ?? checked(breakAfterComments(asWritten(text, result.code, extension), extension)) ?? { error: 'pretty-mismatch' }
|
|
158
242
|
} catch {
|
|
159
243
|
return { error: 'pretty-mismatch' }
|
|
160
244
|
}
|
package/src/types.node.js
CHANGED
|
@@ -194,9 +194,9 @@ export function proveWith(text, stripTypeScriptTypes) {
|
|
|
194
194
|
// type specifier or a comment within it does not count; the hashbang, a directive and a pragma stand
|
|
195
195
|
// verbatim, since what they say is the point. A triple-slash directive counts only in the prologue, before
|
|
196
196
|
// the first statement, where TypeScript honours it; a comment blanked within an erased statement reads as
|
|
197
|
-
// blanks here, so only a kept one steers. `loads` is whether any run statement is a module load
|
|
198
|
-
// `export {}`, the runtime that runs differently per module format
|
|
199
|
-
// not.
|
|
197
|
+
// blanks here, so only a kept one steers. `loads` is whether any run statement is a module load, a bare
|
|
198
|
+
// `export {}`, or an `export default` on an identifier, the runtime that runs differently per module format
|
|
199
|
+
// -- a hashbang, directive or pragma does not.
|
|
200
200
|
const prologue = tree.program.body.length > 0 ? tree.program.body[0].start : stripped.code.length
|
|
201
201
|
const hashbang = tree.program.hashbang ? [tree.program.hashbang] : []
|
|
202
202
|
const steering = tree.comments.filter(({ start, end }) => steers(stripped.code.slice(start, end), start < prologue))
|