rapydscript-ng 0.7.24 → 0.8.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/CHANGELOG.md +19 -0
- package/README.md +72 -26
- package/bin/package.json +1 -0
- package/bin/rapydscript +65 -62
- package/editor-plugins/nvim/rapydscript/README.md +184 -0
- package/editor-plugins/nvim/rapydscript/bin/rapydscript.so +0 -0
- package/editor-plugins/nvim/rapydscript/ftdetect/rapydscript.lua +10 -0
- package/editor-plugins/nvim/rapydscript/lua/rapydscript/health.lua +88 -0
- package/editor-plugins/nvim/rapydscript/lua/rapydscript/init.lua +279 -0
- package/local-agent.md +28 -0
- package/package.json +4 -5
- package/release/baselib-plain-pretty.js +448 -326
- package/release/baselib-plain-ugly.js +3 -3
- package/release/compiler.js +2528 -1672
- package/release/signatures.json +17 -17
- package/src/ast.pyj +73 -25
- package/src/baselib-builtins.pyj +2 -2
- package/src/baselib-containers.pyj +153 -151
- package/src/baselib-internal.pyj +3 -3
- package/src/baselib-str.pyj +5 -5
- package/src/lib/aes.pyj +1 -1
- package/src/lib/encodings.pyj +1 -1
- package/src/lib/pythonize.pyj +1 -1
- package/src/lib/re.pyj +2 -2
- package/src/lib/traceback.pyj +165 -28
- package/src/lib/uuid.pyj +1 -1
- package/src/output/codegen.pyj +10 -3
- package/src/output/functions.pyj +31 -40
- package/src/output/literals.pyj +11 -14
- package/src/output/loops.pyj +25 -87
- package/src/output/modules.pyj +5 -47
- package/src/output/statements.pyj +1 -1
- package/src/output/stream.pyj +58 -31
- package/src/parse.pyj +777 -427
- package/src/tokenizer.pyj +16 -4
- package/src/utils.pyj +1 -1
- package/test/_treeshake_mod.pyj +30 -0
- package/test/_treeshake_side_effect.pyj +9 -0
- package/test/ast_serialization.pyj +392 -0
- package/test/async.pyj +95 -0
- package/test/baselib.pyj +1 -2
- package/test/embedded_compiler.pyj +57 -0
- package/test/fmt.pyj +201 -0
- package/test/generic.pyj +7 -3
- package/test/imports.pyj +8 -6
- package/test/internationalization.pyj +38 -37
- package/test/lint.pyj +120 -117
- package/test/lsp.pyj +222 -0
- package/test/repl.pyj +70 -65
- package/test/sourcemaps.pyj +315 -0
- package/test/starargs.pyj +46 -39
- package/test/traceback.pyj +263 -0
- package/test/treeshaking.pyj +181 -0
- package/test/web_repl_export.pyj +88 -0
- package/tools/ast_serialize.mjs +557 -0
- package/tools/cli.mjs +510 -0
- package/tools/compile.mjs +201 -0
- package/tools/compiler.mjs +127 -0
- package/tools/{completer.js → completer.mjs} +6 -6
- package/tools/{embedded_compiler.js → embedded_compiler.mjs} +17 -13
- package/tools/export.js +109 -56
- package/tools/fmt.mjs +735 -0
- package/tools/{gettext.js → gettext.mjs} +46 -53
- package/tools/{ini.js → ini.mjs} +9 -10
- package/tools/{lint.js → lint.mjs} +78 -55
- package/tools/lsp.mjs +914 -0
- package/tools/lsp_protocol.mjs +259 -0
- package/tools/lsp_symbols.mjs +418 -0
- package/tools/{msgfmt.js → msgfmt.mjs} +18 -18
- package/tools/{repl.js → repl.mjs} +52 -41
- package/tools/{self.js → self.mjs} +56 -46
- package/tools/sourcemap.mjs +123 -0
- package/tools/test.mjs +152 -0
- package/tools/treeshake.mjs +400 -0
- package/tools/{utils.js → utils.mjs} +26 -23
- package/tools/{web_repl.js → web_repl.mjs} +15 -13
- package/tools/web_repl_export.mjs +93 -0
- package/tree-sitter/README.md +101 -0
- package/tree-sitter/grammar.js +992 -0
- package/tree-sitter/package.json +43 -0
- package/tree-sitter/queries/rapydscript/highlights.scm +232 -0
- package/tree-sitter/queries/rapydscript/indents.scm +64 -0
- package/tree-sitter/queries/rapydscript/injections.scm +14 -0
- package/tree-sitter/queries/rapydscript/locals.scm +108 -0
- package/tree-sitter/src/grammar.json +4976 -0
- package/tree-sitter/src/node-types.json +2901 -0
- package/tree-sitter/src/parser.c +196465 -0
- package/tree-sitter/src/scanner.c +294 -0
- package/tree-sitter/src/tree_sitter/alloc.h +54 -0
- package/tree-sitter/src/tree_sitter/array.h +330 -0
- package/tree-sitter/src/tree_sitter/parser.h +286 -0
- package/tree-sitter/test/corpus/chaining.txt +99 -0
- package/tree-sitter/test/corpus/classes.txt +147 -0
- package/tree-sitter/test/corpus/comprehensions.txt +155 -0
- package/tree-sitter/test/corpus/containers.txt +242 -0
- package/tree-sitter/test/corpus/control_flow.txt +361 -0
- package/tree-sitter/test/corpus/decorators.txt +64 -0
- package/tree-sitter/test/corpus/existential.txt +102 -0
- package/tree-sitter/test/corpus/functions.txt +293 -0
- package/tree-sitter/test/corpus/imports.txt +117 -0
- package/tree-sitter/test/corpus/literals.txt +135 -0
- package/tree-sitter/test/corpus/operators.txt +296 -0
- package/tree-sitter/test/corpus/statements.txt +189 -0
- package/tree-sitter/test/corpus/strings.txt +90 -0
- package/tree-sitter/test/corpus/subscripts.txt +227 -0
- package/tree-sitter/tree-sitter.json +36 -0
- package/web-repl/env.js +35 -23
- package/web-repl/main.js +8 -8
- package/bin/export +0 -75
- package/bin/web-repl-export +0 -102
- package/tools/cli.js +0 -523
- package/tools/compile.js +0 -184
- package/tools/compiler.js +0 -84
- package/tools/test.js +0 -110
package/tools/fmt.mjs
ADDED
|
@@ -0,0 +1,735 @@
|
|
|
1
|
+
/* vim:fileencoding=utf-8
|
|
2
|
+
*
|
|
3
|
+
* fmt.mjs -- A PEP8 style code formatter for RapydScript source code.
|
|
4
|
+
*
|
|
5
|
+
* Copyright (C) 2026 Kovid Goyal <kovid at kovidgoyal.net>
|
|
6
|
+
*
|
|
7
|
+
* Distributed under terms of the BSD license.
|
|
8
|
+
*
|
|
9
|
+
* The formatter deliberately does not reuse the compiler's tokenizer because
|
|
10
|
+
* that tokenizer is lossy for a formatter: it decodes string escapes, converts
|
|
11
|
+
* numbers to JavaScript values, turns regexps into RegExp objects, rewrites
|
|
12
|
+
* f-strings by mutating the source buffer and maps operators (and -> &&, is ->
|
|
13
|
+
* === etc.). Instead we use a small, purpose built, loss-less lexer that
|
|
14
|
+
* preserves the exact source lexeme of every token.
|
|
15
|
+
*
|
|
16
|
+
* RapydScript is close to, but not identical to, Python. In particular it
|
|
17
|
+
* supports multi-line anonymous functions, leading-dot call chaining, verbatim
|
|
18
|
+
* JavaScript string/regex literals and newline sensitive blocks. Reformatting
|
|
19
|
+
* these constructs incorrectly would change program semantics, so the formatter
|
|
20
|
+
* is conservative: "complex" logical lines (those containing anonymous
|
|
21
|
+
* functions, block colons that span lines, semicolon inlined blocks, leading
|
|
22
|
+
* dot chains, backslash continuations or multi-line string/regex literals) have
|
|
23
|
+
* their spacing/indentation/quotes normalized but their physical line structure
|
|
24
|
+
* preserved -- they are never reflowed onto a single line nor wrapped.
|
|
25
|
+
*/
|
|
26
|
+
"use strict";
|
|
27
|
+
|
|
28
|
+
import fs from 'fs';
|
|
29
|
+
import path from 'path';
|
|
30
|
+
|
|
31
|
+
// ---------------------------------------------------------------------------
|
|
32
|
+
// Lexer
|
|
33
|
+
// ---------------------------------------------------------------------------
|
|
34
|
+
|
|
35
|
+
var ATOMS = new Set(['True', 'False', 'None']);
|
|
36
|
+
// Keywords that stay keywords (word operators like and/or/not/in/is/new/del are
|
|
37
|
+
// classified as operators, mirroring the compiler's tokenizer).
|
|
38
|
+
var KEYWORDS = new Set([
|
|
39
|
+
'as', 'assert', 'async', 'await', 'break', 'class', 'continue', 'def', 'do',
|
|
40
|
+
'elif', 'else', 'except', 'finally', 'for', 'from', 'global', 'if', 'import',
|
|
41
|
+
'nonlocal', 'pass', 'raise', 'return', 'yield', 'try', 'while', 'with',
|
|
42
|
+
]);
|
|
43
|
+
var WORD_OPS = new Set(['and', 'or', 'not', 'in', 'is', 'new', 'del', 'void', 'typeof', 'instanceof']);
|
|
44
|
+
// After these tokens a `/` begins a regular expression rather than division.
|
|
45
|
+
var KW_BEFORE_EXPR = new Set(['return', 'yield', 'raise', 'elif', 'else', 'if', 'await', 'in', 'assert', 'while']);
|
|
46
|
+
var PUNC_BEFORE_EXPR = new Set(['[', '{', '(', ',', '.', ';', ':']);
|
|
47
|
+
var PUNC_CHARS = new Set(['(', ')', '[', ']', '{', '}', ',', ';', ':', '?']);
|
|
48
|
+
var STRING_MOD = /^[vrufVRUF]+$/;
|
|
49
|
+
|
|
50
|
+
// Symbolic operators, ordered longest-first for greedy matching.
|
|
51
|
+
var OPERATORS = [
|
|
52
|
+
'>>>=', '>>>', '>>=', '<<=', '//=', '**=', '>>', '<<', '//', '**', '<=',
|
|
53
|
+
'>=', '==', '!=', '+=', '-=', '*=', '/=', '%=', '&=', '|=', '^=', '->',
|
|
54
|
+
'~', '+', '-', '*', '/', '%', '<', '>', '=', '&', '|', '^', '@', '!',
|
|
55
|
+
];
|
|
56
|
+
var OPERATOR_CHARS = new Set('+-*&%=<>!?|~^@/'.split(''));
|
|
57
|
+
|
|
58
|
+
function is_ws(c) { return c === ' ' || c === '\t' || c === '\f' || c === '\v' || c === ' '; }
|
|
59
|
+
function is_digit(c) { return c >= '0' && c <= '9'; }
|
|
60
|
+
function is_ident_start(c) {
|
|
61
|
+
if (c === '_' || c === '$') return true;
|
|
62
|
+
var k = c.charCodeAt(0);
|
|
63
|
+
return (k >= 65 && k <= 90) || (k >= 97 && k <= 122) || k > 127;
|
|
64
|
+
}
|
|
65
|
+
function is_ident_char(c) { return is_ident_start(c) || is_digit(c); }
|
|
66
|
+
|
|
67
|
+
function FormatError(message, line) {
|
|
68
|
+
var e = new Error(message + (line ? ' (line ' + line + ')' : ''));
|
|
69
|
+
e.is_format_error = true;
|
|
70
|
+
return e;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function lex(text) {
|
|
74
|
+
var n = text.length;
|
|
75
|
+
var pos = 0, line = 1;
|
|
76
|
+
var toks = [];
|
|
77
|
+
var prev_sig = null; // previous non-comment token, for regex/kind decisions
|
|
78
|
+
|
|
79
|
+
function regex_allowed() {
|
|
80
|
+
if (!prev_sig) return true;
|
|
81
|
+
if (prev_sig.type === 'op') return true;
|
|
82
|
+
if (prev_sig.type === 'keyword' && KW_BEFORE_EXPR.has(prev_sig.value)) return true;
|
|
83
|
+
if (prev_sig.type === 'punc' && PUNC_BEFORE_EXPR.has(prev_sig.value)) return true;
|
|
84
|
+
return false;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function consume_ws() {
|
|
88
|
+
var nlb = 0, had_ws = false, cont = false;
|
|
89
|
+
while (pos < n) {
|
|
90
|
+
var c = text[pos];
|
|
91
|
+
if (c === '\\' && text[pos + 1] === '\n') { pos += 2; line++; nlb++; cont = true; continue; }
|
|
92
|
+
if (c === '\n') { nlb++; line++; pos++; had_ws = true; continue; }
|
|
93
|
+
if (is_ws(c)) { had_ws = true; pos++; continue; }
|
|
94
|
+
break;
|
|
95
|
+
}
|
|
96
|
+
// Leading whitespace of the current physical line.
|
|
97
|
+
var ls = pos;
|
|
98
|
+
while (ls > 0 && text[ls - 1] !== '\n') ls--;
|
|
99
|
+
var ie = ls;
|
|
100
|
+
while (ie < n && is_ws(text[ie])) ie++;
|
|
101
|
+
return { nlb: nlb, sp: (had_ws && nlb === 0), cont: cont, indent: text.slice(ls, ie) };
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function read_string_body(qpos) {
|
|
105
|
+
// qpos points at the opening quote (prefix already consumed). Advances pos
|
|
106
|
+
// past the closing quote and returns nothing; caller slices the lexeme.
|
|
107
|
+
var q = text[qpos];
|
|
108
|
+
if (text[qpos + 1] === q && text[qpos + 2] === q) {
|
|
109
|
+
pos = qpos + 3;
|
|
110
|
+
while (pos < n) {
|
|
111
|
+
if (text[pos] === '\\') { pos += 2; continue; }
|
|
112
|
+
if (text[pos] === q && text[pos + 1] === q && text[pos + 2] === q) { pos += 3; return; }
|
|
113
|
+
if (text[pos] === '\n') line++;
|
|
114
|
+
pos++;
|
|
115
|
+
}
|
|
116
|
+
throw FormatError('Unterminated triple-quoted string', line);
|
|
117
|
+
}
|
|
118
|
+
pos = qpos + 1;
|
|
119
|
+
while (pos < n) {
|
|
120
|
+
var c = text[pos];
|
|
121
|
+
if (c === '\\') { pos += 2; continue; }
|
|
122
|
+
if (c === q) { pos++; return; }
|
|
123
|
+
if (c === '\n') throw FormatError('End of line while scanning string literal', line);
|
|
124
|
+
pos++;
|
|
125
|
+
}
|
|
126
|
+
throw FormatError('Unterminated string', line);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function read_regexp() {
|
|
130
|
+
var start = pos;
|
|
131
|
+
pos++; // first '/'
|
|
132
|
+
if (text[pos] === '/') {
|
|
133
|
+
pos++;
|
|
134
|
+
if (text[pos] === '/') {
|
|
135
|
+
pos++; // verbose ///.../// regex
|
|
136
|
+
while (pos < n) {
|
|
137
|
+
if (text[pos] === '\\') { pos += 2; continue; }
|
|
138
|
+
if (text[pos] === '/' && text[pos + 1] === '/' && text[pos + 2] === '/') { pos += 3; break; }
|
|
139
|
+
if (text[pos] === '\n') line++;
|
|
140
|
+
pos++;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
// else: empty regexp //, nothing more to read before modifiers
|
|
144
|
+
} else {
|
|
145
|
+
var in_class = false;
|
|
146
|
+
while (pos < n) {
|
|
147
|
+
var c = text[pos];
|
|
148
|
+
if (c === '\\') { pos += 2; continue; }
|
|
149
|
+
if (c === '[') { in_class = true; pos++; continue; }
|
|
150
|
+
if (c === ']' && in_class) { in_class = false; pos++; continue; }
|
|
151
|
+
if (c === '/' && !in_class) { pos++; break; }
|
|
152
|
+
if (c === '\n') { throw FormatError('Unterminated regular expression', line); }
|
|
153
|
+
pos++;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
while (pos < n && /[a-zA-Z]/.test(text[pos])) pos++;
|
|
157
|
+
return text.slice(start, pos);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function read_number(from_dot) {
|
|
161
|
+
var start = pos;
|
|
162
|
+
if (!from_dot && text[pos] === '0' && (text[pos + 1] === 'x' || text[pos + 1] === 'X')) {
|
|
163
|
+
pos += 2;
|
|
164
|
+
while (pos < n && /[0-9a-fA-F]/.test(text[pos])) pos++;
|
|
165
|
+
} else if (!from_dot && text[pos] === '0' && (text[pos + 1] === 'b' || text[pos + 1] === 'B')) {
|
|
166
|
+
pos += 2;
|
|
167
|
+
while (pos < n && (text[pos] === '0' || text[pos] === '1')) pos++;
|
|
168
|
+
} else {
|
|
169
|
+
if (from_dot) { pos++; while (pos < n && is_digit(text[pos])) pos++; }
|
|
170
|
+
else {
|
|
171
|
+
while (pos < n && is_digit(text[pos])) pos++;
|
|
172
|
+
if (text[pos] === '.') { pos++; while (pos < n && is_digit(text[pos])) pos++; }
|
|
173
|
+
}
|
|
174
|
+
if (text[pos] === 'e' || text[pos] === 'E') {
|
|
175
|
+
pos++;
|
|
176
|
+
if (text[pos] === '+' || text[pos] === '-') pos++;
|
|
177
|
+
while (pos < n && is_digit(text[pos])) pos++;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
return text.slice(start, pos);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
function match_operator() {
|
|
184
|
+
for (var i = 0; i < OPERATORS.length; i++) {
|
|
185
|
+
if (text.startsWith(OPERATORS[i], pos)) return OPERATORS[i];
|
|
186
|
+
}
|
|
187
|
+
return text[pos];
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
function push(type, value, ws) {
|
|
191
|
+
var t = {
|
|
192
|
+
type: type, value: value, nlb: ws.nlb, sp: ws.sp, cont: ws.cont,
|
|
193
|
+
line_indent: ws.indent, line: line,
|
|
194
|
+
};
|
|
195
|
+
toks.push(t);
|
|
196
|
+
if (type !== 'comment' && type !== 'shebang') prev_sig = t;
|
|
197
|
+
return t;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
while (pos < n) {
|
|
201
|
+
var ws = consume_ws();
|
|
202
|
+
if (pos >= n) break;
|
|
203
|
+
var c = text[pos];
|
|
204
|
+
|
|
205
|
+
if (c === '#') {
|
|
206
|
+
var c_start = pos;
|
|
207
|
+
var eol = text.indexOf('\n', pos);
|
|
208
|
+
if (eol === -1) eol = n;
|
|
209
|
+
var raw = text.slice(pos, eol);
|
|
210
|
+
pos = eol;
|
|
211
|
+
if (c_start === 0 && raw[1] === '!') push('shebang', raw, ws);
|
|
212
|
+
else push('comment', raw, ws);
|
|
213
|
+
continue;
|
|
214
|
+
}
|
|
215
|
+
if (c === '"' || c === "'") {
|
|
216
|
+
var s = pos;
|
|
217
|
+
read_string_body(pos);
|
|
218
|
+
push('string', text.slice(s, pos), ws);
|
|
219
|
+
continue;
|
|
220
|
+
}
|
|
221
|
+
if (is_ident_start(c)) {
|
|
222
|
+
var ws_start = pos;
|
|
223
|
+
while (pos < n && is_ident_char(text[pos])) pos++;
|
|
224
|
+
var word = text.slice(ws_start, pos);
|
|
225
|
+
if (STRING_MOD.test(word) && (text[pos] === '"' || text[pos] === "'")) {
|
|
226
|
+
var qpos = pos;
|
|
227
|
+
read_string_body(pos);
|
|
228
|
+
push('string', word + text.slice(qpos, pos), ws);
|
|
229
|
+
continue;
|
|
230
|
+
}
|
|
231
|
+
if (ATOMS.has(word)) push('atom', word, ws);
|
|
232
|
+
else if (WORD_OPS.has(word)) push('op', word, ws);
|
|
233
|
+
else if (KEYWORDS.has(word)) push('keyword', word, ws);
|
|
234
|
+
else push('name', word, ws);
|
|
235
|
+
continue;
|
|
236
|
+
}
|
|
237
|
+
if (is_digit(c)) { push('number', read_number(false), ws); continue; }
|
|
238
|
+
if (c === '.') {
|
|
239
|
+
if (is_digit(text[pos + 1])) { push('number', read_number(true), ws); continue; }
|
|
240
|
+
pos++; push('punc', '.', ws); continue;
|
|
241
|
+
}
|
|
242
|
+
if (c === '/') {
|
|
243
|
+
if (regex_allowed()) { push('regexp', read_regexp(), ws); continue; }
|
|
244
|
+
var op = match_operator(); pos += op.length; push('op', op, ws); continue;
|
|
245
|
+
}
|
|
246
|
+
if (PUNC_CHARS.has(c)) { pos++; push('punc', c, ws); continue; }
|
|
247
|
+
if (OPERATOR_CHARS.has(c)) { var op2 = match_operator(); pos += op2.length; push('op', op2, ws); continue; }
|
|
248
|
+
throw FormatError('Unexpected character «' + c + '»', line);
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
push('eof', '', { nlb: 0, sp: false, cont: false, indent: '' });
|
|
252
|
+
return toks;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
// ---------------------------------------------------------------------------
|
|
256
|
+
// Token rendering (spacing engine)
|
|
257
|
+
// ---------------------------------------------------------------------------
|
|
258
|
+
|
|
259
|
+
function is_operand(p) {
|
|
260
|
+
if (!p) return false;
|
|
261
|
+
if (p.type === 'name' || p.type === 'number' || p.type === 'string' || p.type === 'atom' || p.type === 'regexp') return true;
|
|
262
|
+
return p.value === ')' || p.value === ']' || p.value === '}';
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
function is_call_paren(p) {
|
|
266
|
+
if (!p) return false;
|
|
267
|
+
if (p.type === 'name' || p.type === 'string' || p.type === 'atom') return true;
|
|
268
|
+
if (p.value === 'def') return true;
|
|
269
|
+
return p.value === ')' || p.value === ']' || p.value === '}';
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
function is_index_bracket(p) {
|
|
273
|
+
if (!p) return false;
|
|
274
|
+
if (p.type === 'name' || p.type === 'string' || p.type === 'number' || p.type === 'atom') return true;
|
|
275
|
+
return p.value === ')' || p.value === ']' || p.value === '}';
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
function is_unary_here(p, t) {
|
|
279
|
+
if (t.type === 'op') {
|
|
280
|
+
var v = t.value;
|
|
281
|
+
if (v === '+' || v === '-' || v === '~' || v === '*' || v === '**') return !is_operand(p);
|
|
282
|
+
if (v === '@' && p === null) return true;
|
|
283
|
+
}
|
|
284
|
+
return false;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
function separator(p, t, stack, prev_was_unary, sig_count, first_at) {
|
|
288
|
+
var pv = p.value, tv = t.value, pty = p.type, tty = t.type;
|
|
289
|
+
var top = stack.length ? stack[stack.length - 1] : null;
|
|
290
|
+
if (pty === 'punc' && (pv === '(' || pv === '[' || pv === '{')) return false;
|
|
291
|
+
if (tty === 'punc' && (tv === ')' || tv === ']' || tv === '}')) return false;
|
|
292
|
+
if (tv === ',' || tv === ';') return false;
|
|
293
|
+
if (tv === '.' || pv === '.') return false;
|
|
294
|
+
if (tv === '?' || pv === '?') return !!t.sp; // existential: preserve source spacing
|
|
295
|
+
if (tv === ':') return false;
|
|
296
|
+
if (pv === ':') { if (top && top.kind === 'index') return false; return true; }
|
|
297
|
+
if (pv === '@' && first_at && sig_count === 1) return false; // decorator
|
|
298
|
+
if (tv === '(' && is_call_paren(p)) return false;
|
|
299
|
+
if (tv === '[' && is_index_bracket(p)) return false;
|
|
300
|
+
if ((tv === '=' || pv === '=') && top && top.kind === 'call') return false; // kwarg / default
|
|
301
|
+
if (prev_was_unary) return false;
|
|
302
|
+
return true;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
function format_comment(raw) {
|
|
306
|
+
var m = raw.match(/^#+/)[0];
|
|
307
|
+
var rest = raw.slice(m.length).replace(/\s+$/, '');
|
|
308
|
+
if (rest === '') return m;
|
|
309
|
+
if (rest[0] !== ' ' && rest[0] !== '!') rest = ' ' + rest;
|
|
310
|
+
return m + rest;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
function normalize_quotes(lex_val, preferred) {
|
|
314
|
+
var i = 0;
|
|
315
|
+
while (i < lex_val.length && /[vrufVRUF]/.test(lex_val[i])) i++;
|
|
316
|
+
var prefix = lex_val.slice(0, i);
|
|
317
|
+
var q = lex_val[i];
|
|
318
|
+
if (q !== "'" && q !== '"') return lex_val;
|
|
319
|
+
var lower = prefix.toLowerCase();
|
|
320
|
+
if (lower.indexOf('v') >= 0 || lower.indexOf('r') >= 0 || lower.indexOf('f') >= 0) return lex_val;
|
|
321
|
+
if (lex_val[i + 1] === q && lex_val[i + 2] === q) return lex_val; // triple-quoted: leave as-is
|
|
322
|
+
var target = preferred;
|
|
323
|
+
if (q === target) return lex_val;
|
|
324
|
+
var body = lex_val.slice(i + 1, lex_val.length - 1);
|
|
325
|
+
var nb = '', j = 0;
|
|
326
|
+
while (j < body.length) {
|
|
327
|
+
var c = body[j];
|
|
328
|
+
if (c === '\\' && j + 1 < body.length) {
|
|
329
|
+
var nx = body[j + 1];
|
|
330
|
+
if (nx === q) { nb += q; j += 2; continue; } // escaped old quote -> unescape
|
|
331
|
+
nb += '\\' + nx; j += 2; continue;
|
|
332
|
+
}
|
|
333
|
+
if (c === target) { nb += '\\' + target; j++; continue; } // must escape target
|
|
334
|
+
nb += c; j++;
|
|
335
|
+
}
|
|
336
|
+
var count = function (s) { var k = 0; for (var x = 0; x < s.length; x++) if (s[x] === '\\') k++; return k; };
|
|
337
|
+
if (count(nb) > count(body)) return lex_val; // don't increase escapes
|
|
338
|
+
return prefix + target + nb + target;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
function token_text(t, opts) {
|
|
342
|
+
if (t.type === 'string') return normalize_quotes(t.value, opts.preferred);
|
|
343
|
+
return t.value;
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
// Render a list of tokens to a string. When honor_breaks is true, source line
|
|
347
|
+
// breaks (nlb>0) are preserved as newlines + re-based indentation; otherwise
|
|
348
|
+
// everything is emitted on one line (reflow).
|
|
349
|
+
function render(toks, honor_breaks, src_base, new_base, opts, initial_stack) {
|
|
350
|
+
var out = '';
|
|
351
|
+
var stack = initial_stack ? initial_stack.slice() : [];
|
|
352
|
+
var prev_sig = null, prev_was_unary = false, sig_count = 0, first_at = false;
|
|
353
|
+
for (var k = 0; k < toks.length; k++) {
|
|
354
|
+
var t = toks[k];
|
|
355
|
+
var did_break = false;
|
|
356
|
+
if (k > 0 && honor_breaks && t.nlb > 0) {
|
|
357
|
+
out += (t.cont ? ' \\\n' : '\n');
|
|
358
|
+
var li = t.line_indent || '';
|
|
359
|
+
// Rebase indentation that shares the statement's base prefix; preserve
|
|
360
|
+
// dedented continuation lines verbatim (e.g. a leading-dot line that
|
|
361
|
+
// binds to an outer block, or a `.while` clause of a do/while loop).
|
|
362
|
+
if (li.startsWith(src_base)) out += new_base + li.slice(src_base.length);
|
|
363
|
+
else out += li;
|
|
364
|
+
did_break = true;
|
|
365
|
+
}
|
|
366
|
+
if (t.type === 'comment' || t.type === 'shebang') {
|
|
367
|
+
var ctext = (t.type === 'shebang') ? t.value : format_comment(t.value);
|
|
368
|
+
if (k === 0 || did_break) out += ctext;
|
|
369
|
+
else out += ' ' + ctext;
|
|
370
|
+
continue;
|
|
371
|
+
}
|
|
372
|
+
if (k > 0 && !did_break && prev_sig !== null) {
|
|
373
|
+
if (separator(prev_sig, t, stack, prev_was_unary, sig_count, first_at)) out += ' ';
|
|
374
|
+
}
|
|
375
|
+
out += token_text(t, opts);
|
|
376
|
+
prev_was_unary = is_unary_here(prev_sig, t);
|
|
377
|
+
if (t.type === 'punc') {
|
|
378
|
+
if (t.value === '(') stack.push({ kind: is_call_paren(prev_sig) ? 'call' : 'group' });
|
|
379
|
+
else if (t.value === '[') stack.push({ kind: is_index_bracket(prev_sig) ? 'index' : 'list' });
|
|
380
|
+
else if (t.value === '{') stack.push({ kind: 'dict' });
|
|
381
|
+
else if (t.value === ')' || t.value === ']' || t.value === '}') { if (stack.length) stack.pop(); }
|
|
382
|
+
}
|
|
383
|
+
if (sig_count === 0 && t.value === '@' && t.type === 'op') first_at = true;
|
|
384
|
+
prev_sig = t; sig_count++;
|
|
385
|
+
}
|
|
386
|
+
return out;
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
// ---------------------------------------------------------------------------
|
|
390
|
+
// Line wrapping (conservative)
|
|
391
|
+
// ---------------------------------------------------------------------------
|
|
392
|
+
|
|
393
|
+
function wrap_line(code, level, opts) {
|
|
394
|
+
// Locate the top-level (depth 0 -> 1) bracket group with commas and the
|
|
395
|
+
// largest span, then explode it across multiple lines with hanging indent.
|
|
396
|
+
var depth = 0, open_stack = [], groups = [];
|
|
397
|
+
for (var i = 0; i < code.length; i++) {
|
|
398
|
+
var v = code[i].value, ty = code[i].type;
|
|
399
|
+
if (ty === 'punc' && (v === '(' || v === '[' || v === '{')) { if (depth === 0) open_stack.push({ open: i, ch: v }); depth++; }
|
|
400
|
+
else if (ty === 'punc' && (v === ')' || v === ']' || v === '}')) { depth--; if (depth === 0 && open_stack.length) { var g = open_stack.pop(); g.close = i; groups.push(g); } }
|
|
401
|
+
}
|
|
402
|
+
var best = null;
|
|
403
|
+
for (var gi = 0; gi < groups.length; gi++) {
|
|
404
|
+
var grp = groups[gi];
|
|
405
|
+
if (grp.close === undefined) continue;
|
|
406
|
+
var d = 0, has_comma = false, is_comprehension = false;
|
|
407
|
+
for (var x = grp.open + 1; x < grp.close; x++) {
|
|
408
|
+
var vv = code[x].value, tt = code[x].type;
|
|
409
|
+
if (tt === 'punc' && (vv === '(' || vv === '[' || vv === '{')) d++;
|
|
410
|
+
else if (tt === 'punc' && (vv === ')' || vv === ']' || vv === '}')) d--;
|
|
411
|
+
else if (d === 0 && tt === 'punc' && vv === ',') has_comma = true;
|
|
412
|
+
else if (d === 0 && tt === 'keyword' && vv === 'for') is_comprehension = true;
|
|
413
|
+
}
|
|
414
|
+
// In a comprehension/generator the top-level commas are tuple unpacking,
|
|
415
|
+
// not element separators, so it is not safe to explode on them.
|
|
416
|
+
if (!has_comma || is_comprehension) continue;
|
|
417
|
+
var span = grp.close - grp.open;
|
|
418
|
+
if (!best || span > best.span) best = { open: grp.open, close: grp.close, ch: grp.ch, span: span };
|
|
419
|
+
}
|
|
420
|
+
if (!best) return null;
|
|
421
|
+
|
|
422
|
+
var base_indent = ' '.repeat(4 * level);
|
|
423
|
+
var child_indent = ' '.repeat(4 * (level + 1));
|
|
424
|
+
var open_ch = best.ch;
|
|
425
|
+
var seed_kind = (open_ch === '(') ? (is_call_paren(best.open > 0 ? code[best.open - 1] : null) ? 'call' : 'group')
|
|
426
|
+
: (open_ch === '[') ? (is_index_bracket(best.open > 0 ? code[best.open - 1] : null) ? 'index' : 'list')
|
|
427
|
+
: 'dict';
|
|
428
|
+
|
|
429
|
+
var head = code.slice(0, best.open + 1);
|
|
430
|
+
var head_str = base_indent + render(head, false, '', base_indent, opts);
|
|
431
|
+
|
|
432
|
+
var inner = code.slice(best.open + 1, best.close);
|
|
433
|
+
var elems = [], cur = [], dd = 0;
|
|
434
|
+
for (var y = 0; y < inner.length; y++) {
|
|
435
|
+
var tk = inner[y], iv = tk.value, it = tk.type;
|
|
436
|
+
if (it === 'punc' && (iv === '(' || iv === '[' || iv === '{')) { dd++; cur.push(tk); }
|
|
437
|
+
else if (it === 'punc' && (iv === ')' || iv === ']' || iv === '}')) { dd--; cur.push(tk); }
|
|
438
|
+
else if (dd === 0 && it === 'punc' && iv === ',') { elems.push(cur); cur = []; }
|
|
439
|
+
else cur.push(tk);
|
|
440
|
+
}
|
|
441
|
+
if (cur.length) elems.push(cur);
|
|
442
|
+
elems = elems.filter(function (e) { return e.length; });
|
|
443
|
+
if (!elems.length) return null;
|
|
444
|
+
|
|
445
|
+
var elem_lines = elems.map(function (e) { return child_indent + render(e, false, '', child_indent, opts, [{ kind: seed_kind }]); });
|
|
446
|
+
var body = elem_lines.join(',\n');
|
|
447
|
+
if (open_ch === '[' || open_ch === '{') body += ','; // magic trailing comma for literals only
|
|
448
|
+
|
|
449
|
+
var tail = code.slice(best.close);
|
|
450
|
+
var tail_str = base_indent + render(tail, false, '', base_indent, opts);
|
|
451
|
+
return head_str + '\n' + body + '\n' + tail_str;
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
// ---------------------------------------------------------------------------
|
|
455
|
+
// Grouping into logical lines / elements
|
|
456
|
+
// ---------------------------------------------------------------------------
|
|
457
|
+
|
|
458
|
+
function make_stmt(tokens) {
|
|
459
|
+
var sig = tokens.filter(function (t) { return t.type !== 'comment' && t.type !== 'shebang'; });
|
|
460
|
+
var first_sig = sig[0], last_sig = sig[sig.length - 1];
|
|
461
|
+
var has_def_or_class = sig.some(function (t) { return t.type === 'keyword' && (t.value === 'def' || t.value === 'class'); });
|
|
462
|
+
var has_semicolon = sig.some(function (t) { return t.value === ';'; });
|
|
463
|
+
// A logical line that starts with `.` or that contains a physical line
|
|
464
|
+
// beginning with `.` is a leading-dot chain (or a do/while `.while` clause).
|
|
465
|
+
// These bind to an outer block by indentation, so we preserve their layout.
|
|
466
|
+
var leading_dot = (first_sig && first_sig.value === '.') ||
|
|
467
|
+
tokens.some(function (t, i) { return i > 0 && t.nlb > 0 && t.type === 'punc' && t.value === '.'; });
|
|
468
|
+
var used_backslash = tokens.some(function (t) { return t.cont; });
|
|
469
|
+
var multi_physical = tokens.some(function (t, i) { return i > 0 && t.nlb > 0; });
|
|
470
|
+
var colon_then_newline = false;
|
|
471
|
+
for (var i = 0; i < tokens.length - 1; i++) { if (tokens[i].value === ':' && tokens[i + 1].nlb > 0) { colon_then_newline = true; break; } }
|
|
472
|
+
var has_multiline_token = tokens.some(function (t) { return typeof t.value === 'string' && t.value.indexOf('\n') >= 0; });
|
|
473
|
+
var interior_comment = false;
|
|
474
|
+
for (var j = 0; j < tokens.length; j++) {
|
|
475
|
+
if (tokens[j].type === 'comment') {
|
|
476
|
+
if (j < tokens.length - 1) interior_comment = true;
|
|
477
|
+
else if (tokens[j].nlb > 0) interior_comment = true;
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
var complex = leading_dot || used_backslash || interior_comment || has_def_or_class ||
|
|
481
|
+
has_semicolon || has_multiline_token || (multi_physical && colon_then_newline);
|
|
482
|
+
|
|
483
|
+
var inline_body_colon = false, d = 0;
|
|
484
|
+
for (var s = 0; s < sig.length; s++) {
|
|
485
|
+
var v = sig[s].value, ty = sig[s].type;
|
|
486
|
+
if (ty === 'punc' && (v === '(' || v === '[' || v === '{')) d++;
|
|
487
|
+
else if (ty === 'punc' && (v === ')' || v === ']' || v === '}')) d--;
|
|
488
|
+
else if (d === 0 && v === ':' && s < sig.length - 1) inline_body_colon = true;
|
|
489
|
+
}
|
|
490
|
+
var wrappable = !complex && !has_def_or_class && !has_semicolon && !inline_body_colon;
|
|
491
|
+
var is_def_like = first_sig && ((first_sig.type === 'keyword' && (first_sig.value === 'def' || first_sig.value === 'class')) || first_sig.value === 'async');
|
|
492
|
+
var is_decorator = first_sig && first_sig.value === '@';
|
|
493
|
+
var ends_block = last_sig && last_sig.value === ':';
|
|
494
|
+
return {
|
|
495
|
+
kind: 'stmt', tokens: tokens, indent: tokens[0].line_indent || '',
|
|
496
|
+
blank_before: Math.max(0, tokens[0].nlb - 1), complex: complex, wrappable: wrappable,
|
|
497
|
+
is_def_like: !!is_def_like, is_decorator: !!is_decorator, ends_block: !!ends_block, level: 0,
|
|
498
|
+
};
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
function group(toks) {
|
|
502
|
+
var elements = [];
|
|
503
|
+
var cur = [], depth = 0;
|
|
504
|
+
function flush() { if (cur.length) { elements.push(make_stmt(cur)); cur = []; } }
|
|
505
|
+
for (var k = 0; k < toks.length; k++) {
|
|
506
|
+
var t = toks[k];
|
|
507
|
+
if (t.type === 'eof') break;
|
|
508
|
+
var is_boundary = depth === 0 && cur.length > 0 && t.nlb > 0 && !t.cont && !(t.value === '.' && t.type === 'punc');
|
|
509
|
+
if (is_boundary) flush();
|
|
510
|
+
if (depth === 0 && cur.length === 0 && (t.type === 'comment' || t.type === 'shebang') && (t.nlb > 0 || k === 0)) {
|
|
511
|
+
elements.push({
|
|
512
|
+
kind: 'comment', value: t.value, is_shebang: t.type === 'shebang',
|
|
513
|
+
indent: t.line_indent || '', blank_before: (k === 0 ? 0 : Math.max(0, t.nlb - 1)),
|
|
514
|
+
level: 0, ends_block: false,
|
|
515
|
+
});
|
|
516
|
+
continue;
|
|
517
|
+
}
|
|
518
|
+
cur.push(t);
|
|
519
|
+
if (t.type === 'punc') {
|
|
520
|
+
if (t.value === '(' || t.value === '[' || t.value === '{') depth++;
|
|
521
|
+
else if (t.value === ')' || t.value === ']' || t.value === '}') depth = Math.max(0, depth - 1);
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
flush();
|
|
525
|
+
return elements;
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
function compute_levels(elements) {
|
|
529
|
+
var stack = [{ w: -1 }];
|
|
530
|
+
for (var i = 0; i < elements.length; i++) {
|
|
531
|
+
var el = elements[i];
|
|
532
|
+
if (el.kind !== 'stmt') continue;
|
|
533
|
+
var w = el.indent.length;
|
|
534
|
+
if (w > stack[stack.length - 1].w) stack.push({ w: w });
|
|
535
|
+
else {
|
|
536
|
+
while (stack.length > 1 && w < stack[stack.length - 1].w) stack.pop();
|
|
537
|
+
if (w > stack[stack.length - 1].w) stack.push({ w: w });
|
|
538
|
+
}
|
|
539
|
+
el.level = stack.length - 2;
|
|
540
|
+
if (el.level < 0) el.level = 0;
|
|
541
|
+
}
|
|
542
|
+
// Comments take the level of the following statement (or the previous one at EOF).
|
|
543
|
+
for (var c = 0; c < elements.length; c++) {
|
|
544
|
+
if (elements[c].kind !== 'comment') continue;
|
|
545
|
+
var lvl = null;
|
|
546
|
+
for (var f = c + 1; f < elements.length; f++) { if (elements[f].kind === 'stmt') { lvl = elements[f].level; break; } }
|
|
547
|
+
if (lvl === null) { for (var b = c - 1; b >= 0; b--) { if (elements[b].kind === 'stmt') { lvl = elements[b].level; break; } } }
|
|
548
|
+
elements[c].level = lvl === null ? 0 : lvl;
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
// ---------------------------------------------------------------------------
|
|
553
|
+
// Blank line policy helpers
|
|
554
|
+
// ---------------------------------------------------------------------------
|
|
555
|
+
|
|
556
|
+
function leads_def(elements, idx) {
|
|
557
|
+
var j = idx;
|
|
558
|
+
while (j < elements.length) {
|
|
559
|
+
var e = elements[j];
|
|
560
|
+
if (e.kind === 'comment') {
|
|
561
|
+
if (j + 1 < elements.length && elements[j + 1].blank_before === 0) { j++; continue; }
|
|
562
|
+
return false;
|
|
563
|
+
}
|
|
564
|
+
if (e.kind === 'stmt') {
|
|
565
|
+
if (e.is_decorator) { if (j + 1 < elements.length && elements[j + 1].blank_before === 0) { j++; continue; } return false; }
|
|
566
|
+
return !!e.is_def_like;
|
|
567
|
+
}
|
|
568
|
+
return false;
|
|
569
|
+
}
|
|
570
|
+
return false;
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
function is_visual_def_start(elements, idx) {
|
|
574
|
+
if (!leads_def(elements, idx)) return false;
|
|
575
|
+
if (idx === 0) return true;
|
|
576
|
+
var prev = elements[idx - 1], el = elements[idx];
|
|
577
|
+
if (el.blank_before === 0 && (prev.kind === 'comment' || (prev.kind === 'stmt' && prev.is_decorator))) return false;
|
|
578
|
+
return true;
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
// ---------------------------------------------------------------------------
|
|
582
|
+
// Element rendering + assembly
|
|
583
|
+
// ---------------------------------------------------------------------------
|
|
584
|
+
|
|
585
|
+
function render_stmt(el, opts) {
|
|
586
|
+
var base_indent = ' '.repeat(4 * el.level);
|
|
587
|
+
if (el.complex) {
|
|
588
|
+
return base_indent + render(el.tokens, true, el.indent, base_indent, opts);
|
|
589
|
+
}
|
|
590
|
+
var comment = null, code = el.tokens;
|
|
591
|
+
if (code.length && code[code.length - 1].type === 'comment') { comment = code[code.length - 1]; code = code.slice(0, -1); }
|
|
592
|
+
var line = render(code, false, el.indent, base_indent, opts);
|
|
593
|
+
var full = base_indent + line;
|
|
594
|
+
var comment_str = comment ? ' ' + format_comment(comment.value) : '';
|
|
595
|
+
if ((full.length + comment_str.length) > opts.line_length && el.wrappable) {
|
|
596
|
+
var wrapped = wrap_line(code, el.level, opts);
|
|
597
|
+
if (wrapped !== null) return comment_str ? wrapped + comment_str : wrapped;
|
|
598
|
+
}
|
|
599
|
+
return full + comment_str;
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
export function format_string(src, options) {
|
|
603
|
+
var opts = normalize_opts(options);
|
|
604
|
+
var text = src.replace(/\r\n?/g, '\n');
|
|
605
|
+
var toks = lex(text);
|
|
606
|
+
var elements = group(toks);
|
|
607
|
+
compute_levels(elements);
|
|
608
|
+
|
|
609
|
+
var result = '';
|
|
610
|
+
for (var idx = 0; idx < elements.length; idx++) {
|
|
611
|
+
var el = elements[idx];
|
|
612
|
+
var etext = (el.kind === 'comment')
|
|
613
|
+
? (' '.repeat(4 * el.level) + (el.is_shebang ? el.value : format_comment(el.value)))
|
|
614
|
+
: render_stmt(el, opts);
|
|
615
|
+
var blanks = 0;
|
|
616
|
+
if (idx > 0) {
|
|
617
|
+
var prev = elements[idx - 1];
|
|
618
|
+
var cap = (el.level === 0) ? 2 : 1;
|
|
619
|
+
blanks = Math.min(el.blank_before, cap);
|
|
620
|
+
if (prev.ends_block) blanks = 0;
|
|
621
|
+
if (is_visual_def_start(elements, idx)) {
|
|
622
|
+
var req = (el.level === 0) ? 2 : 1;
|
|
623
|
+
if (!prev.ends_block) blanks = Math.max(blanks, req);
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
if (idx === 0) result += etext;
|
|
627
|
+
else result += '\n' + '\n'.repeat(blanks) + etext;
|
|
628
|
+
}
|
|
629
|
+
if (result === '') return '';
|
|
630
|
+
return result + '\n';
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
function normalize_opts(options) {
|
|
634
|
+
options = options || {};
|
|
635
|
+
var ll = options.line_length;
|
|
636
|
+
if (typeof ll === 'string') ll = parseInt(ll, 10);
|
|
637
|
+
if (!ll || isNaN(ll) || ll < 1) ll = 80;
|
|
638
|
+
var pref = options.preferred;
|
|
639
|
+
if (!pref) {
|
|
640
|
+
var pq = options.preferred_quote;
|
|
641
|
+
pref = (pq === 'double' || pq === '"') ? '"' : "'";
|
|
642
|
+
}
|
|
643
|
+
return { line_length: ll, preferred: pref };
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
// ---------------------------------------------------------------------------
|
|
647
|
+
// check-only reporting
|
|
648
|
+
// ---------------------------------------------------------------------------
|
|
649
|
+
|
|
650
|
+
export function check_report(file, src, formatted, options) {
|
|
651
|
+
var opts = normalize_opts(options);
|
|
652
|
+
var errs = [];
|
|
653
|
+
if (formatted !== src.replace(/\r\n?/g, '\n')) errs.push(file + ': would be reformatted');
|
|
654
|
+
var lines = formatted.split('\n');
|
|
655
|
+
for (var i = 0; i < lines.length; i++) {
|
|
656
|
+
if (lines[i].length > opts.line_length) {
|
|
657
|
+
errs.push(file + ':' + (i + 1) + ': line exceeds ' + opts.line_length + ' characters (' + lines[i].length + ')');
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
return errs;
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
// ---------------------------------------------------------------------------
|
|
664
|
+
// File collection + CLI
|
|
665
|
+
// ---------------------------------------------------------------------------
|
|
666
|
+
|
|
667
|
+
export async function collect_pyj_files(inputs) {
|
|
668
|
+
var files = [];
|
|
669
|
+
async function walk(list, from_dir) {
|
|
670
|
+
for (var i = 0; i < list.length; i++) {
|
|
671
|
+
var f = list[i];
|
|
672
|
+
var st;
|
|
673
|
+
try { st = await fs.promises.lstat(f); }
|
|
674
|
+
catch (e) { throw new Error("can't access: " + f); }
|
|
675
|
+
if (st.isDirectory()) {
|
|
676
|
+
var children = (await fs.promises.readdir(f)).sort().map(function (x) { return path.join(f, x); });
|
|
677
|
+
await walk(children, true);
|
|
678
|
+
} else if (st.isFile()) {
|
|
679
|
+
if (from_dir) { if (f.endsWith('.pyj')) files.push(f); }
|
|
680
|
+
else files.push(f);
|
|
681
|
+
}
|
|
682
|
+
}
|
|
683
|
+
}
|
|
684
|
+
await walk(inputs, false);
|
|
685
|
+
return files;
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
async function read_stdin() {
|
|
689
|
+
var chunks = [];
|
|
690
|
+
process.stdin.setEncoding('utf-8');
|
|
691
|
+
await new Promise(function (resolve, reject) {
|
|
692
|
+
process.stdin.on('data', function (chunk) { chunks.push(chunk); });
|
|
693
|
+
process.stdin.on('end', resolve);
|
|
694
|
+
process.stdin.on('error', reject);
|
|
695
|
+
});
|
|
696
|
+
return chunks.join('');
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
export async function cli(argv, base_path, src_path, lib_path) {
|
|
700
|
+
var opts = normalize_opts({ line_length: argv.line_length, preferred_quote: argv.preferred_quote });
|
|
701
|
+
var inputs = (argv.files || []).slice();
|
|
702
|
+
|
|
703
|
+
if (inputs.length === 0) {
|
|
704
|
+
var src = await read_stdin();
|
|
705
|
+
var out;
|
|
706
|
+
try { out = format_string(src, opts); }
|
|
707
|
+
catch (e) { console.error('Error formatting stdin: ' + (e.message || e)); process.exit(2); }
|
|
708
|
+
process.stdout.write(out);
|
|
709
|
+
process.exit(0);
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
var files;
|
|
713
|
+
try { files = await collect_pyj_files(inputs); }
|
|
714
|
+
catch (e) { console.error('ERROR: ' + (e.message || e)); process.exit(2); }
|
|
715
|
+
|
|
716
|
+
var had_errors = false;
|
|
717
|
+
for (var i = 0; i < files.length; i++) {
|
|
718
|
+
var f = files[i];
|
|
719
|
+
var code;
|
|
720
|
+
try { code = await fs.promises.readFile(f, 'utf-8'); }
|
|
721
|
+
catch (e) { console.error("ERROR: can't read file: " + f); process.exit(2); }
|
|
722
|
+
var formatted;
|
|
723
|
+
try { formatted = format_string(code, opts); }
|
|
724
|
+
catch (e) { console.error(f + ': ' + (e.message || e)); had_errors = true; continue; }
|
|
725
|
+
if (argv.check_only) {
|
|
726
|
+
var errs = check_report(f, code, formatted, opts);
|
|
727
|
+
if (errs.length) { had_errors = true; errs.forEach(function (m) { console.error(m); }); }
|
|
728
|
+
} else if (formatted !== code.replace(/\r\n?/g, '\n')) {
|
|
729
|
+
try { await fs.promises.writeFile(f, formatted); }
|
|
730
|
+
catch (e) { console.error("ERROR: can't write file: " + f); process.exit(2); }
|
|
731
|
+
console.log('reformatted ' + f);
|
|
732
|
+
}
|
|
733
|
+
}
|
|
734
|
+
process.exit(had_errors ? 1 : 0);
|
|
735
|
+
}
|