vladx 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +256 -0
- package/bin/cli.js +486 -0
- package/bin/vlad.js +539 -0
- package/bin/vladpm.js +710 -0
- package/bin/vladx.js +491 -0
- package/package.json +57 -0
- package/src/engine/jit-compiler.js +285 -0
- package/src/engine/vladx-engine.js +941 -0
- package/src/index.js +44 -0
- package/src/interpreter/interpreter.js +2114 -0
- package/src/lexer/lexer.js +658 -0
- package/src/lexer/optimized-lexer.js +106 -0
- package/src/lexer/regex-cache.js +83 -0
- package/src/parser/ast-nodes.js +472 -0
- package/src/parser/parser.js +1408 -0
- package/src/runtime/advanced-type-system.js +209 -0
- package/src/runtime/async-manager.js +252 -0
- package/src/runtime/builtins.js +143 -0
- package/src/runtime/bundler.js +422 -0
- package/src/runtime/cache-manager.js +126 -0
- package/src/runtime/data-structures.js +612 -0
- package/src/runtime/debugger.js +260 -0
- package/src/runtime/enhanced-module-system.js +196 -0
- package/src/runtime/environment-enhanced.js +272 -0
- package/src/runtime/environment.js +140 -0
- package/src/runtime/event-emitter.js +232 -0
- package/src/runtime/formatter.js +280 -0
- package/src/runtime/functional.js +359 -0
- package/src/runtime/io-operations.js +390 -0
- package/src/runtime/linter.js +374 -0
- package/src/runtime/logging.js +314 -0
- package/src/runtime/minifier.js +242 -0
- package/src/runtime/module-system.js +377 -0
- package/src/runtime/network-operations.js +373 -0
- package/src/runtime/profiler.js +295 -0
- package/src/runtime/repl.js +336 -0
- package/src/runtime/security-manager.js +244 -0
- package/src/runtime/source-map-generator.js +208 -0
- package/src/runtime/test-runner.js +394 -0
- package/src/runtime/transformer.js +277 -0
- package/src/runtime/type-system.js +244 -0
- package/src/runtime/vladx-object.js +250 -0
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* VladX Environment — Окружение выполнения
|
|
3
|
+
* Управляет областями видимости переменных
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export class Environment {
|
|
7
|
+
constructor(parent = null, name = '<anonymous>') {
|
|
8
|
+
this.parent = parent;
|
|
9
|
+
this.name = name;
|
|
10
|
+
this.variables = new Map();
|
|
11
|
+
this.constants = new Set();
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Определение переменной
|
|
16
|
+
*/
|
|
17
|
+
define(name, value, isConst = false) {
|
|
18
|
+
// Check if it's already defined as a constant in the current environment only
|
|
19
|
+
// Local variables should be able to shadow parent constants/functions
|
|
20
|
+
if (this.constants.has(name)) {
|
|
21
|
+
throw new Error(`Константа ${name} уже объявлена`);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
if (this.variables.has(name) && !isConst) {
|
|
25
|
+
// Предупреждение о переопределении
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
this.variables.set(name, value);
|
|
29
|
+
|
|
30
|
+
if (isConst) {
|
|
31
|
+
this.constants.add(name);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Получение значения переменной
|
|
37
|
+
*/
|
|
38
|
+
get(name) {
|
|
39
|
+
if (this.variables.has(name)) {
|
|
40
|
+
return this.variables.get(name);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (this.parent) {
|
|
44
|
+
return this.parent.get(name);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
throw new Error(`Переменная "${name}" не найдена в окружении ${this.name}`);
|
|
48
|
+
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Присваивание значения переменной
|
|
53
|
+
*/
|
|
54
|
+
assign(name, value) {
|
|
55
|
+
if (this.variables.has(name)) {
|
|
56
|
+
if (this.constants.has(name)) {
|
|
57
|
+
throw new Error(`Нельзя изменить константу ${name}`);
|
|
58
|
+
}
|
|
59
|
+
this.variables.set(name, value);
|
|
60
|
+
return true;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
if (this.parent) {
|
|
64
|
+
return this.parent.assign(name, value);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
throw new Error(`Переменная ${name} не найдена`);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Проверка существования переменной
|
|
72
|
+
*/
|
|
73
|
+
has(name) {
|
|
74
|
+
return this.variables.has(name) || (this.parent && this.parent.has(name));
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Создание дочернего окружения
|
|
79
|
+
*/
|
|
80
|
+
child(name = '<child>') {
|
|
81
|
+
return new Environment(this, name);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Клонирование окружения
|
|
86
|
+
*/
|
|
87
|
+
clone() {
|
|
88
|
+
const cloned = new Environment(this.parent, this.name);
|
|
89
|
+
|
|
90
|
+
for (const [key, value] of this.variables) {
|
|
91
|
+
cloned.variables.set(key, value);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
for (const constant of this.constants) {
|
|
95
|
+
cloned.constants.add(constant);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
return cloned;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Получение всех переменных
|
|
103
|
+
*/
|
|
104
|
+
getAll() {
|
|
105
|
+
const result = {};
|
|
106
|
+
|
|
107
|
+
if (this.parent) {
|
|
108
|
+
Object.assign(result, this.parent.getAll());
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
for (const [key, value] of this.variables) {
|
|
112
|
+
result[key] = value;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
return result;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Удаление переменной
|
|
120
|
+
*/
|
|
121
|
+
delete(name) {
|
|
122
|
+
if (this.variables.has(name)) {
|
|
123
|
+
this.variables.delete(name);
|
|
124
|
+
this.constants.delete(name);
|
|
125
|
+
return true;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
return false;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Очистка окружения
|
|
133
|
+
*/
|
|
134
|
+
clear() {
|
|
135
|
+
this.variables.clear();
|
|
136
|
+
this.constants.clear();
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
export default Environment;
|
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* EventEmitter — Реализация события emitter
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
export class EventEmitter {
|
|
6
|
+
constructor() {
|
|
7
|
+
this.events = new Map();
|
|
8
|
+
this.onceEvents = new Map();
|
|
9
|
+
this.maxListeners = 10;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Добавить listener
|
|
14
|
+
*/
|
|
15
|
+
on(event, listener) {
|
|
16
|
+
if (!this.events.has(event)) {
|
|
17
|
+
this.events.set(event, []);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const listeners = this.events.get(event);
|
|
21
|
+
|
|
22
|
+
// Проверка на maxListeners
|
|
23
|
+
if (listeners.length >= this.maxListeners) {
|
|
24
|
+
console.warn(`Possible memory leak detected. ${listeners.length} ${event} listeners added.`);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
listeners.push(listener);
|
|
28
|
+
return this;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Добавить one-time listener
|
|
33
|
+
*/
|
|
34
|
+
once(event, listener) {
|
|
35
|
+
const wrapper = (...args) => {
|
|
36
|
+
this.off(event, wrapper);
|
|
37
|
+
listener(...args);
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
this.on(event, wrapper);
|
|
41
|
+
return this;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Удалить listener
|
|
46
|
+
*/
|
|
47
|
+
off(event, listener) {
|
|
48
|
+
const listeners = this.events.get(event);
|
|
49
|
+
if (listeners) {
|
|
50
|
+
const index = listeners.indexOf(listener);
|
|
51
|
+
if (index !== -1) {
|
|
52
|
+
listeners.splice(index, 1);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (listeners.length === 0) {
|
|
56
|
+
this.events.delete(event);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
return this;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Запустить событие
|
|
64
|
+
*/
|
|
65
|
+
emit(event, ...args) {
|
|
66
|
+
const listeners = this.events.get(event);
|
|
67
|
+
if (listeners) {
|
|
68
|
+
// Копируем массив для безопасной модификации во время итерации
|
|
69
|
+
const listenersCopy = [...listeners];
|
|
70
|
+
|
|
71
|
+
for (const listener of listenersCopy) {
|
|
72
|
+
try {
|
|
73
|
+
listener(...args);
|
|
74
|
+
} catch (error) {
|
|
75
|
+
// Ошибка в listener не должна прерывать других
|
|
76
|
+
console.error(`Error in ${event} listener:`, error);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return listeners.length > 0;
|
|
81
|
+
}
|
|
82
|
+
return false;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Добавить listener (синоним on)
|
|
87
|
+
*/
|
|
88
|
+
addListener(event, listener) {
|
|
89
|
+
return this.on(event, listener);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Удалить listener (синоним off)
|
|
94
|
+
*/
|
|
95
|
+
removeListener(event, listener) {
|
|
96
|
+
return this.off(event, listener);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Удалить все listeners для события
|
|
101
|
+
*/
|
|
102
|
+
removeAllListeners(event) {
|
|
103
|
+
if (event) {
|
|
104
|
+
this.events.delete(event);
|
|
105
|
+
} else {
|
|
106
|
+
this.events.clear();
|
|
107
|
+
}
|
|
108
|
+
return this;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Получить listeners для события
|
|
113
|
+
*/
|
|
114
|
+
listeners(event) {
|
|
115
|
+
return this.events.get(event) || [];
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Получить количество listeners для события
|
|
120
|
+
*/
|
|
121
|
+
listenerCount(event) {
|
|
122
|
+
return (this.events.get(event) || []).length;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Получить все имена событий
|
|
127
|
+
*/
|
|
128
|
+
eventNames() {
|
|
129
|
+
return Array.from(this.events.keys());
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Установить максимальное количество listeners
|
|
134
|
+
*/
|
|
135
|
+
setMaxListeners(n) {
|
|
136
|
+
this.maxListeners = n;
|
|
137
|
+
return this;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Получить максимальное количество listeners
|
|
142
|
+
*/
|
|
143
|
+
getMaxListeners() {
|
|
144
|
+
return this.maxListeners;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Добавить prepend listener (добавляется в начало)
|
|
149
|
+
*/
|
|
150
|
+
prependListener(event, listener) {
|
|
151
|
+
if (!this.events.has(event)) {
|
|
152
|
+
this.events.set(event, []);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
this.events.get(event).unshift(listener);
|
|
156
|
+
return this;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Добавить prepend once listener
|
|
161
|
+
*/
|
|
162
|
+
prependOnceListener(event, listener) {
|
|
163
|
+
const wrapper = (...args) => {
|
|
164
|
+
this.off(event, wrapper);
|
|
165
|
+
listener(...args);
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
this.prependListener(event, wrapper);
|
|
169
|
+
return this;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Статический метод для создания emitter
|
|
174
|
+
*/
|
|
175
|
+
static create() {
|
|
176
|
+
return new EventEmitter();
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Асинхронный emit (Promise)
|
|
181
|
+
*/
|
|
182
|
+
async emitAsync(event, ...args) {
|
|
183
|
+
const listeners = this.events.get(event);
|
|
184
|
+
if (!listeners) {
|
|
185
|
+
return false;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
const results = [];
|
|
189
|
+
|
|
190
|
+
for (const listener of listeners) {
|
|
191
|
+
try {
|
|
192
|
+
const result = listener(...args);
|
|
193
|
+
if (result && typeof result.then === 'function') {
|
|
194
|
+
results.push(await result);
|
|
195
|
+
} else {
|
|
196
|
+
results.push(result);
|
|
197
|
+
}
|
|
198
|
+
} catch (error) {
|
|
199
|
+
console.error(`Error in ${event} async listener:`, error);
|
|
200
|
+
results.push(Promise.reject(error));
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
return results;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* Проверить, есть ли listeners
|
|
209
|
+
*/
|
|
210
|
+
hasListeners(event) {
|
|
211
|
+
return this.listenerCount(event) > 0;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* Получить информацию о памяти
|
|
216
|
+
*/
|
|
217
|
+
getMemoryUsage() {
|
|
218
|
+
let totalListeners = 0;
|
|
219
|
+
|
|
220
|
+
for (const [event, listeners] of this.events) {
|
|
221
|
+
totalListeners += listeners.length;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
return {
|
|
225
|
+
events: this.events.size,
|
|
226
|
+
totalListeners,
|
|
227
|
+
maxListeners: this.maxListeners
|
|
228
|
+
};
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
export default EventEmitter;
|
|
@@ -0,0 +1,280 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Formatter — Форматирование кода VladX
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { Lexer } from '../lexer/lexer.js';
|
|
6
|
+
import { Parser } from '../parser/parser.js';
|
|
7
|
+
|
|
8
|
+
export class Formatter {
|
|
9
|
+
constructor(options = {}) {
|
|
10
|
+
this.indentSize = options.indentSize || 4;
|
|
11
|
+
this.indentChar = options.indentChar || ' ';
|
|
12
|
+
this.useTabs = options.useTabs || false;
|
|
13
|
+
this.semicolons = options.semicolons !== false;
|
|
14
|
+
this.trailingComma = options.trailingComma || false;
|
|
15
|
+
this.singleQuote = options.singleQuote || false;
|
|
16
|
+
this.printWidth = options.printWidth || 100;
|
|
17
|
+
this.insertFinalNewline = options.insertFinalNewline !== false;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Отформатировать код
|
|
22
|
+
*/
|
|
23
|
+
format(source, filename = '<anonymous>') {
|
|
24
|
+
let formatted = source;
|
|
25
|
+
|
|
26
|
+
// Шаг 1: Нормализация пробелов
|
|
27
|
+
formatted = this.normalizeWhitespace(formatted);
|
|
28
|
+
|
|
29
|
+
// Шаг 2: Форматирование блоков
|
|
30
|
+
formatted = this.formatBlocks(formatted);
|
|
31
|
+
|
|
32
|
+
// Шаг 3: Форматирование отступов
|
|
33
|
+
formatted = this.formatIndentation(formatted);
|
|
34
|
+
|
|
35
|
+
// Шаг 4: Форматирование операторов
|
|
36
|
+
formatted = this.formatOperators(formatted);
|
|
37
|
+
|
|
38
|
+
// Шаг 5: Удаление лишних пустых строк
|
|
39
|
+
formatted = this.formatEmptyLines(formatted);
|
|
40
|
+
|
|
41
|
+
// Шаг 6: Добавление перевода строки в конце
|
|
42
|
+
if (this.insertFinalNewline && !formatted.endsWith('\n')) {
|
|
43
|
+
formatted += '\n';
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return formatted;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Нормализация пробелов
|
|
51
|
+
*/
|
|
52
|
+
normalizeWhitespace(source) {
|
|
53
|
+
// Удалить trailing whitespace
|
|
54
|
+
let formatted = source.split('\n').map(line => line.trimEnd()).join('\n');
|
|
55
|
+
|
|
56
|
+
// Нормализировать табы в пробелы или наоборот
|
|
57
|
+
if (this.useTabs) {
|
|
58
|
+
formatted = formatted.replace(/ {4}/g, '\t');
|
|
59
|
+
} else {
|
|
60
|
+
formatted = formatted.replace(/\t/g, ' '.repeat(this.indentSize));
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return formatted;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Форматирование блоков
|
|
68
|
+
*/
|
|
69
|
+
formatBlocks(source) {
|
|
70
|
+
let formatted = source;
|
|
71
|
+
|
|
72
|
+
// Форматирование if/else
|
|
73
|
+
formatted = formatted.replace(
|
|
74
|
+
/(?:если|if)\s*\(([^)]*)\)\s*([^{])/g,
|
|
75
|
+
(match, condition, nextChar) => {
|
|
76
|
+
const indent = ' '.repeat(this.indentSize);
|
|
77
|
+
return `если (${condition}) {\n${indent}${nextChar}`;
|
|
78
|
+
}
|
|
79
|
+
);
|
|
80
|
+
|
|
81
|
+
// Форматирование while
|
|
82
|
+
formatted = formatted.replace(
|
|
83
|
+
/(?:пока|while)\s*\(([^)]*)\)\s*([^{])/g,
|
|
84
|
+
(match, condition, nextChar) => {
|
|
85
|
+
const indent = ' '.repeat(this.indentSize);
|
|
86
|
+
return `пока (${condition}) {\n${indent}${nextChar}`;
|
|
87
|
+
}
|
|
88
|
+
);
|
|
89
|
+
|
|
90
|
+
// Форматирование for
|
|
91
|
+
formatted = formatted.replace(
|
|
92
|
+
/(?:для|for)\s*\(([^)]*)\)\s*([^{])/g,
|
|
93
|
+
(match, condition, nextChar) => {
|
|
94
|
+
const indent = ' '.repeat(this.indentSize);
|
|
95
|
+
return `для (${condition}) {\n${indent}${nextChar}`;
|
|
96
|
+
}
|
|
97
|
+
);
|
|
98
|
+
|
|
99
|
+
return formatted;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Форматирование отступов
|
|
104
|
+
*/
|
|
105
|
+
formatIndentation(source) {
|
|
106
|
+
const lines = source.split('\n');
|
|
107
|
+
const formattedLines = [];
|
|
108
|
+
let indentLevel = 0;
|
|
109
|
+
|
|
110
|
+
for (const line of lines) {
|
|
111
|
+
const trimmed = line.trim();
|
|
112
|
+
|
|
113
|
+
if (trimmed.length === 0) {
|
|
114
|
+
formattedLines.push('');
|
|
115
|
+
continue;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// Уменьшить отступ перед закрывающей скобкой
|
|
119
|
+
if (trimmed.startsWith('}') || trimmed.startsWith('иначе')) {
|
|
120
|
+
indentLevel = Math.max(0, indentLevel - 1);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Добавить отступ
|
|
124
|
+
const indent = this.useTabs
|
|
125
|
+
? '\t'.repeat(indentLevel)
|
|
126
|
+
: ' '.repeat(indentLevel * this.indentSize);
|
|
127
|
+
formattedLines.push(indent + trimmed);
|
|
128
|
+
|
|
129
|
+
// Увеличить отступ после открывающей скобки
|
|
130
|
+
if (trimmed.endsWith('{')) {
|
|
131
|
+
indentLevel++;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
return formattedLines.join('\n');
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Форматирование операторов
|
|
140
|
+
*/
|
|
141
|
+
formatOperators(source) {
|
|
142
|
+
let formatted = source;
|
|
143
|
+
|
|
144
|
+
// Пробелы вокруг операторов
|
|
145
|
+
const operators = ['+', '-', '*', '/', '%', '=', '==', '!=', '<', '>', '<=', '>=', '&&', '||', '!'];
|
|
146
|
+
|
|
147
|
+
for (const op of operators) {
|
|
148
|
+
formatted = formatted.replace(
|
|
149
|
+
new RegExp(`([^\\s])\\${op}([^\\s])`, 'g'),
|
|
150
|
+
`$1 ${op} $2`
|
|
151
|
+
);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// Пробелы после запятых
|
|
155
|
+
formatted = formatted.replace(/,(\S)/g, ', $1');
|
|
156
|
+
|
|
157
|
+
// Пробелы после точек с запятой
|
|
158
|
+
formatted = formatted.replace(/;(\S)/g, '; $1');
|
|
159
|
+
|
|
160
|
+
return formatted;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Форматирование пустых строк
|
|
165
|
+
*/
|
|
166
|
+
formatEmptyLines(source) {
|
|
167
|
+
const lines = source.split('\n');
|
|
168
|
+
const formattedLines = [];
|
|
169
|
+
let emptyCount = 0;
|
|
170
|
+
|
|
171
|
+
for (const line of lines) {
|
|
172
|
+
if (line.trim().length === 0) {
|
|
173
|
+
emptyCount++;
|
|
174
|
+
if (emptyCount <= 2) {
|
|
175
|
+
formattedLines.push(line);
|
|
176
|
+
}
|
|
177
|
+
} else {
|
|
178
|
+
emptyCount = 0;
|
|
179
|
+
formattedLines.push(line);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
return formattedLines.join('\n');
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Проверить код
|
|
188
|
+
*/
|
|
189
|
+
check(source, filename = '<anonymous>') {
|
|
190
|
+
const issues = [];
|
|
191
|
+
|
|
192
|
+
// Проверить длину строк
|
|
193
|
+
const lines = source.split('\n');
|
|
194
|
+
lines.forEach((line, index) => {
|
|
195
|
+
if (line.length > this.printWidth) {
|
|
196
|
+
issues.push({
|
|
197
|
+
message: `Строка слишком длинная: ${line.length} > ${this.printWidth}`,
|
|
198
|
+
line: index + 1,
|
|
199
|
+
severity: 'warning'
|
|
200
|
+
});
|
|
201
|
+
}
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
// Проверить trailing whitespace
|
|
205
|
+
lines.forEach((line, index) => {
|
|
206
|
+
if (line !== line.trimEnd()) {
|
|
207
|
+
issues.push({
|
|
208
|
+
message: 'Trailing whitespace в конце строки',
|
|
209
|
+
line: index + 1,
|
|
210
|
+
severity: 'info'
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
// Проверить смешивание табов и пробелов
|
|
216
|
+
lines.forEach((line, index) => {
|
|
217
|
+
if (line.includes('\t') && line.includes(' ')) {
|
|
218
|
+
issues.push({
|
|
219
|
+
message: 'Смешивание табов и пробелов',
|
|
220
|
+
line: index + 1,
|
|
221
|
+
severity: 'error'
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
return {
|
|
227
|
+
issues,
|
|
228
|
+
hasIssues: issues.length > 0
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* Форматировать с автофиксом
|
|
234
|
+
*/
|
|
235
|
+
formatWithFix(source, filename = '<anonymous>') {
|
|
236
|
+
const checkResult = this.check(source, filename);
|
|
237
|
+
|
|
238
|
+
if (!checkResult.hasIssues) {
|
|
239
|
+
return {
|
|
240
|
+
source,
|
|
241
|
+
formatted: false,
|
|
242
|
+
issues: []
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
const formatted = this.format(source, filename);
|
|
247
|
+
|
|
248
|
+
return {
|
|
249
|
+
source: formatted,
|
|
250
|
+
formatted: true,
|
|
251
|
+
issues: checkResult.issues
|
|
252
|
+
};
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
/**
|
|
256
|
+
* Получить конфигурацию
|
|
257
|
+
*/
|
|
258
|
+
getConfig() {
|
|
259
|
+
return {
|
|
260
|
+
indentSize: this.indentSize,
|
|
261
|
+
indentChar: this.indentChar,
|
|
262
|
+
useTabs: this.useTabs,
|
|
263
|
+
semicolons: this.semicolons,
|
|
264
|
+
trailingComma: this.trailingComma,
|
|
265
|
+
singleQuote: this.singleQuote,
|
|
266
|
+
printWidth: this.printWidth,
|
|
267
|
+
insertFinalNewline: this.insertFinalNewline
|
|
268
|
+
};
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
/**
|
|
272
|
+
* Установить конфигурацию
|
|
273
|
+
*/
|
|
274
|
+
setConfig(config) {
|
|
275
|
+
Object.assign(this, config);
|
|
276
|
+
return this;
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
export default Formatter;
|