prettier-plugin-noshift.js 0.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/LICENSE +21 -0
- package/package.json +39 -0
- package/src/convert.js +373 -0
- package/src/index.js +99 -0
- package/src/reverse.js +278 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 [otoneko.]
|
|
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/package.json
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "prettier-plugin-noshift.js",
|
|
3
|
+
"version": "0.0.0",
|
|
4
|
+
"description": "Prettier plugin for NoShift.js",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "src/index.js",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": "./src/index.js"
|
|
9
|
+
},
|
|
10
|
+
"files": [
|
|
11
|
+
"src/"
|
|
12
|
+
],
|
|
13
|
+
"scripts": {
|
|
14
|
+
"test": "node test/test.js"
|
|
15
|
+
},
|
|
16
|
+
"repository": {
|
|
17
|
+
"type": "git",
|
|
18
|
+
"url": "git+https://github.com/otoneko1102/NoShift.js.git"
|
|
19
|
+
},
|
|
20
|
+
"keywords": [
|
|
21
|
+
"noshift",
|
|
22
|
+
"nsjs",
|
|
23
|
+
"joke",
|
|
24
|
+
"prettier",
|
|
25
|
+
"prettier-plugin",
|
|
26
|
+
"format",
|
|
27
|
+
"formatter",
|
|
28
|
+
"fmt"
|
|
29
|
+
],
|
|
30
|
+
"author": "otoneko.",
|
|
31
|
+
"license": "MIT",
|
|
32
|
+
"bugs": {
|
|
33
|
+
"url": "https://github.com/otoneko1102/NoShift.js/issues"
|
|
34
|
+
},
|
|
35
|
+
"homepage": "https://github.com/otoneko1102/NoShift.js#readme",
|
|
36
|
+
"peerDependencies": {
|
|
37
|
+
"prettier": "^3.8.1"
|
|
38
|
+
}
|
|
39
|
+
}
|
package/src/convert.js
ADDED
|
@@ -0,0 +1,373 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* NoShift.js → JavaScript 変換器 (self-contained)
|
|
3
|
+
* prettier-plugin-noshift.js 用のスタンドアロン版
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
// ======
|
|
7
|
+
// NoShift.js → JavaScript 置換用マップ
|
|
8
|
+
// ======
|
|
9
|
+
const noShiftMap = {
|
|
10
|
+
"^4^[": "${", // テンプレート式展開開始
|
|
11
|
+
"^1": "!",
|
|
12
|
+
"^2": '"',
|
|
13
|
+
"^4": "$",
|
|
14
|
+
"^5": "%",
|
|
15
|
+
"^6": "&",
|
|
16
|
+
"^7": "'",
|
|
17
|
+
"^8": "(",
|
|
18
|
+
"^9": ")",
|
|
19
|
+
"^-": "=",
|
|
20
|
+
"^^": "~",
|
|
21
|
+
"^\\": "|",
|
|
22
|
+
"^@": "`",
|
|
23
|
+
"^[": "{",
|
|
24
|
+
"^]": "}",
|
|
25
|
+
"^;": "+",
|
|
26
|
+
"^:": "*",
|
|
27
|
+
"^,": "<",
|
|
28
|
+
"^.": ">",
|
|
29
|
+
"^/": "?",
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* NoShift.js コードを JavaScript コードに変換する。
|
|
34
|
+
* @param {string} nsjsCode
|
|
35
|
+
* @param {{ capitalizeInStrings?: boolean }} [options={}]
|
|
36
|
+
* @returns {string}
|
|
37
|
+
*/
|
|
38
|
+
export function convertNsjsToJs(nsjsCode, options = {}) {
|
|
39
|
+
const capitalizeInStrings = options.capitalizeInStrings !== false;
|
|
40
|
+
let jsCode = "";
|
|
41
|
+
let i = 0;
|
|
42
|
+
const len_ns = nsjsCode.length;
|
|
43
|
+
|
|
44
|
+
const STATE = {
|
|
45
|
+
NORMAL: "NORMAL",
|
|
46
|
+
IN_DQ_STRING: "IN_DQ_STRING",
|
|
47
|
+
IN_SQ_STRING: "IN_SQ_STRING",
|
|
48
|
+
IN_BT_SINGLE_STRING: "IN_BT_SINGLE_STRING",
|
|
49
|
+
IN_BT_MULTI_STRING: "IN_BT_MULTI_STRING",
|
|
50
|
+
IN_TEMPLATE_EXPRESSION: "IN_TEMPLATE_EXPRESSION",
|
|
51
|
+
RAW_DQ_IN_EXPR: "RAW_DQ_IN_EXPR",
|
|
52
|
+
RAW_SQ_IN_EXPR: "RAW_SQ_IN_EXPR",
|
|
53
|
+
IN_LINE_COMMENT: "IN_LINE_COMMENT",
|
|
54
|
+
IN_BLOCK_COMMENT: "IN_BLOCK_COMMENT",
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
let currentState = STATE.NORMAL;
|
|
58
|
+
const stateStack = [];
|
|
59
|
+
|
|
60
|
+
const sortedNsKeys = Object.keys(noShiftMap).sort(
|
|
61
|
+
(a, b) => b.length - a.length,
|
|
62
|
+
);
|
|
63
|
+
|
|
64
|
+
function tryConsumeNsjsSequence() {
|
|
65
|
+
let allowGeneral = false;
|
|
66
|
+
if (
|
|
67
|
+
currentState === STATE.NORMAL ||
|
|
68
|
+
currentState === STATE.IN_TEMPLATE_EXPRESSION
|
|
69
|
+
) {
|
|
70
|
+
allowGeneral = true;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
for (const nsKey of sortedNsKeys) {
|
|
74
|
+
if (!nsjsCode.startsWith(nsKey, i)) continue;
|
|
75
|
+
|
|
76
|
+
// テンプレートリテラル中の式展開開始
|
|
77
|
+
if (
|
|
78
|
+
(nsKey === "^4^[" || nsjsCode.startsWith("^4[", i)) &&
|
|
79
|
+
(currentState === STATE.IN_BT_SINGLE_STRING ||
|
|
80
|
+
currentState === STATE.IN_BT_MULTI_STRING)
|
|
81
|
+
) {
|
|
82
|
+
jsCode += "${";
|
|
83
|
+
i += nsKey === "^4^[" ? nsKey.length : 3;
|
|
84
|
+
stateStack.push(currentState);
|
|
85
|
+
currentState = STATE.IN_TEMPLATE_EXPRESSION;
|
|
86
|
+
return true;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// テンプレート式中の式閉じ
|
|
90
|
+
if (nsKey === "^]" && currentState === STATE.IN_TEMPLATE_EXPRESSION) {
|
|
91
|
+
jsCode += "}";
|
|
92
|
+
i += nsKey.length;
|
|
93
|
+
currentState = stateStack.pop();
|
|
94
|
+
return true;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// ダブルクォート開閉
|
|
98
|
+
if (nsKey === "^2" && currentState === STATE.NORMAL) {
|
|
99
|
+
jsCode += '"';
|
|
100
|
+
i += nsKey.length;
|
|
101
|
+
stateStack.push(currentState);
|
|
102
|
+
currentState = STATE.IN_DQ_STRING;
|
|
103
|
+
return true;
|
|
104
|
+
}
|
|
105
|
+
if (nsKey === "^2" && currentState === STATE.IN_DQ_STRING) {
|
|
106
|
+
jsCode += '"';
|
|
107
|
+
i += nsKey.length;
|
|
108
|
+
currentState = stateStack.pop();
|
|
109
|
+
return true;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// シングルクォート開閉
|
|
113
|
+
if (nsKey === "^7" && currentState === STATE.NORMAL) {
|
|
114
|
+
jsCode += "'";
|
|
115
|
+
i += nsKey.length;
|
|
116
|
+
stateStack.push(currentState);
|
|
117
|
+
currentState = STATE.IN_SQ_STRING;
|
|
118
|
+
return true;
|
|
119
|
+
}
|
|
120
|
+
if (nsKey === "^7" && currentState === STATE.IN_SQ_STRING) {
|
|
121
|
+
jsCode += "'";
|
|
122
|
+
i += nsKey.length;
|
|
123
|
+
currentState = stateStack.pop();
|
|
124
|
+
return true;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// バックチック開閉
|
|
128
|
+
if (
|
|
129
|
+
nsKey === "^@" &&
|
|
130
|
+
(currentState === STATE.NORMAL ||
|
|
131
|
+
currentState === STATE.IN_TEMPLATE_EXPRESSION)
|
|
132
|
+
) {
|
|
133
|
+
jsCode += "`";
|
|
134
|
+
i += nsKey.length;
|
|
135
|
+
stateStack.push(currentState);
|
|
136
|
+
currentState = STATE.IN_BT_SINGLE_STRING;
|
|
137
|
+
return true;
|
|
138
|
+
}
|
|
139
|
+
if (nsKey === "^@" && currentState === STATE.IN_BT_SINGLE_STRING) {
|
|
140
|
+
jsCode += "`";
|
|
141
|
+
i += nsKey.length;
|
|
142
|
+
currentState = stateStack.pop();
|
|
143
|
+
return true;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// テンプレート式中の文字列開閉
|
|
147
|
+
if (nsKey === "^2" && currentState === STATE.IN_TEMPLATE_EXPRESSION) {
|
|
148
|
+
jsCode += '"';
|
|
149
|
+
i += nsKey.length;
|
|
150
|
+
stateStack.push(currentState);
|
|
151
|
+
currentState = STATE.RAW_DQ_IN_EXPR;
|
|
152
|
+
return true;
|
|
153
|
+
}
|
|
154
|
+
if (nsKey === "^7" && currentState === STATE.IN_TEMPLATE_EXPRESSION) {
|
|
155
|
+
jsCode += "'";
|
|
156
|
+
i += nsKey.length;
|
|
157
|
+
stateStack.push(currentState);
|
|
158
|
+
currentState = STATE.RAW_SQ_IN_EXPR;
|
|
159
|
+
return true;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// RAW 文字列終端
|
|
163
|
+
if (nsKey === "^2" && currentState === STATE.RAW_DQ_IN_EXPR) {
|
|
164
|
+
jsCode += '"';
|
|
165
|
+
i += nsKey.length;
|
|
166
|
+
currentState = stateStack.pop();
|
|
167
|
+
return true;
|
|
168
|
+
}
|
|
169
|
+
if (nsKey === "^7" && currentState === STATE.RAW_SQ_IN_EXPR) {
|
|
170
|
+
jsCode += "'";
|
|
171
|
+
i += nsKey.length;
|
|
172
|
+
currentState = stateStack.pop();
|
|
173
|
+
return true;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// 通常の置換
|
|
177
|
+
if (allowGeneral) {
|
|
178
|
+
jsCode += noShiftMap[nsKey];
|
|
179
|
+
i += nsKey.length;
|
|
180
|
+
return true;
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
return false;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// メインループ
|
|
188
|
+
while (i < len_ns) {
|
|
189
|
+
let consumed = false;
|
|
190
|
+
|
|
191
|
+
// エスケープ処理
|
|
192
|
+
if (currentState === STATE.IN_DQ_STRING) {
|
|
193
|
+
if (nsjsCode.startsWith("\\^3", i)) {
|
|
194
|
+
jsCode += "^3";
|
|
195
|
+
i += 3;
|
|
196
|
+
consumed = true;
|
|
197
|
+
} else if (nsjsCode.startsWith("\\^2", i)) {
|
|
198
|
+
jsCode += "^2";
|
|
199
|
+
i += 3;
|
|
200
|
+
consumed = true;
|
|
201
|
+
} else if (nsjsCode.startsWith("\\\\", i)) {
|
|
202
|
+
jsCode += "\\\\\\\\";
|
|
203
|
+
i += 2;
|
|
204
|
+
consumed = true;
|
|
205
|
+
}
|
|
206
|
+
} else if (currentState === STATE.IN_SQ_STRING) {
|
|
207
|
+
if (nsjsCode.startsWith("\\^3", i)) {
|
|
208
|
+
jsCode += "^3";
|
|
209
|
+
i += 3;
|
|
210
|
+
consumed = true;
|
|
211
|
+
} else if (nsjsCode.startsWith("\\^7", i)) {
|
|
212
|
+
jsCode += "^7";
|
|
213
|
+
i += 3;
|
|
214
|
+
consumed = true;
|
|
215
|
+
} else if (nsjsCode.startsWith("\\\\", i)) {
|
|
216
|
+
jsCode += "\\\\\\\\";
|
|
217
|
+
i += 2;
|
|
218
|
+
consumed = true;
|
|
219
|
+
}
|
|
220
|
+
} else if (currentState === STATE.RAW_DQ_IN_EXPR) {
|
|
221
|
+
if (nsjsCode.startsWith("\\^3", i)) {
|
|
222
|
+
jsCode += "^3";
|
|
223
|
+
i += 3;
|
|
224
|
+
consumed = true;
|
|
225
|
+
} else if (nsjsCode.startsWith("\\^2", i)) {
|
|
226
|
+
jsCode += "^2";
|
|
227
|
+
i += 3;
|
|
228
|
+
consumed = true;
|
|
229
|
+
} else if (nsjsCode.startsWith("\\\\", i)) {
|
|
230
|
+
jsCode += "\\\\\\\\";
|
|
231
|
+
i += 2;
|
|
232
|
+
consumed = true;
|
|
233
|
+
} else if (nsjsCode.startsWith("^2", i)) {
|
|
234
|
+
jsCode += '"';
|
|
235
|
+
i += 2;
|
|
236
|
+
currentState = stateStack.pop();
|
|
237
|
+
consumed = true;
|
|
238
|
+
}
|
|
239
|
+
if (consumed) continue;
|
|
240
|
+
jsCode += nsjsCode[i];
|
|
241
|
+
i += 1;
|
|
242
|
+
continue;
|
|
243
|
+
} else if (currentState === STATE.RAW_SQ_IN_EXPR) {
|
|
244
|
+
if (nsjsCode.startsWith("\\^3", i)) {
|
|
245
|
+
jsCode += "^3";
|
|
246
|
+
i += 3;
|
|
247
|
+
consumed = true;
|
|
248
|
+
} else if (nsjsCode.startsWith("\\^7", i)) {
|
|
249
|
+
jsCode += "^7";
|
|
250
|
+
i += 3;
|
|
251
|
+
consumed = true;
|
|
252
|
+
} else if (nsjsCode.startsWith("\\\\", i)) {
|
|
253
|
+
jsCode += "\\\\\\\\";
|
|
254
|
+
i += 2;
|
|
255
|
+
consumed = true;
|
|
256
|
+
} else if (nsjsCode.startsWith("^7", i)) {
|
|
257
|
+
jsCode += "'";
|
|
258
|
+
i += 2;
|
|
259
|
+
currentState = stateStack.pop();
|
|
260
|
+
consumed = true;
|
|
261
|
+
}
|
|
262
|
+
if (consumed) continue;
|
|
263
|
+
jsCode += nsjsCode[i];
|
|
264
|
+
i += 1;
|
|
265
|
+
continue;
|
|
266
|
+
} else if (currentState === STATE.IN_BT_SINGLE_STRING) {
|
|
267
|
+
if (nsjsCode.startsWith("\\^3", i)) {
|
|
268
|
+
jsCode += "^3";
|
|
269
|
+
i += 3;
|
|
270
|
+
consumed = true;
|
|
271
|
+
} else if (nsjsCode.startsWith("\\^@", i)) {
|
|
272
|
+
jsCode += "^@";
|
|
273
|
+
i += 3;
|
|
274
|
+
consumed = true;
|
|
275
|
+
} else if (nsjsCode.startsWith("\\\\", i)) {
|
|
276
|
+
jsCode += "\\\\\\\\";
|
|
277
|
+
i += 2;
|
|
278
|
+
consumed = true;
|
|
279
|
+
}
|
|
280
|
+
} else if (currentState === STATE.IN_BT_MULTI_STRING) {
|
|
281
|
+
if (nsjsCode.startsWith("\\\\", i)) {
|
|
282
|
+
jsCode += "\\\\\\\\";
|
|
283
|
+
i += 2;
|
|
284
|
+
consumed = true;
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
// ^3 大文字化モディファイア
|
|
289
|
+
if (
|
|
290
|
+
!consumed &&
|
|
291
|
+
currentState !== STATE.RAW_DQ_IN_EXPR &&
|
|
292
|
+
currentState !== STATE.RAW_SQ_IN_EXPR &&
|
|
293
|
+
currentState !== STATE.IN_LINE_COMMENT &&
|
|
294
|
+
currentState !== STATE.IN_BLOCK_COMMENT
|
|
295
|
+
) {
|
|
296
|
+
if (nsjsCode.startsWith("^3", i)) {
|
|
297
|
+
const inString =
|
|
298
|
+
currentState === STATE.IN_DQ_STRING ||
|
|
299
|
+
currentState === STATE.IN_SQ_STRING ||
|
|
300
|
+
currentState === STATE.IN_BT_SINGLE_STRING ||
|
|
301
|
+
currentState === STATE.IN_BT_MULTI_STRING;
|
|
302
|
+
if (!inString || capitalizeInStrings) {
|
|
303
|
+
i += 2;
|
|
304
|
+
if (i < len_ns) {
|
|
305
|
+
jsCode += nsjsCode[i].toUpperCase();
|
|
306
|
+
i += 1;
|
|
307
|
+
}
|
|
308
|
+
consumed = true;
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
// コメント処理
|
|
314
|
+
if (!consumed) {
|
|
315
|
+
if (currentState === STATE.NORMAL && nsjsCode.startsWith("//", i)) {
|
|
316
|
+
jsCode += "//";
|
|
317
|
+
i += 2;
|
|
318
|
+
stateStack.push(currentState);
|
|
319
|
+
currentState = STATE.IN_LINE_COMMENT;
|
|
320
|
+
consumed = true;
|
|
321
|
+
} else if (currentState === STATE.IN_LINE_COMMENT) {
|
|
322
|
+
if (nsjsCode[i] === "\n") {
|
|
323
|
+
jsCode += "\n";
|
|
324
|
+
i += 1;
|
|
325
|
+
currentState = stateStack.pop();
|
|
326
|
+
} else {
|
|
327
|
+
jsCode += nsjsCode[i];
|
|
328
|
+
i += 1;
|
|
329
|
+
}
|
|
330
|
+
consumed = true;
|
|
331
|
+
} else if (
|
|
332
|
+
currentState === STATE.NORMAL &&
|
|
333
|
+
nsjsCode.startsWith("/^:", i)
|
|
334
|
+
) {
|
|
335
|
+
jsCode += "/*";
|
|
336
|
+
i += 3;
|
|
337
|
+
stateStack.push(currentState);
|
|
338
|
+
currentState = STATE.IN_BLOCK_COMMENT;
|
|
339
|
+
consumed = true;
|
|
340
|
+
} else if (
|
|
341
|
+
currentState === STATE.IN_BLOCK_COMMENT &&
|
|
342
|
+
nsjsCode.startsWith("^:/", i)
|
|
343
|
+
) {
|
|
344
|
+
jsCode += "*/";
|
|
345
|
+
i += 3;
|
|
346
|
+
currentState = stateStack.pop();
|
|
347
|
+
consumed = true;
|
|
348
|
+
} else if (currentState === STATE.IN_BLOCK_COMMENT) {
|
|
349
|
+
jsCode += nsjsCode[i];
|
|
350
|
+
i += 1;
|
|
351
|
+
consumed = true;
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
// NoShift シーケンス
|
|
356
|
+
if (!consumed) {
|
|
357
|
+
consumed = tryConsumeNsjsSequence();
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
// そのまま出力
|
|
361
|
+
if (!consumed) {
|
|
362
|
+
jsCode += nsjsCode[i];
|
|
363
|
+
i += 1;
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
if (stateStack.length > 0) {
|
|
368
|
+
console.warn(
|
|
369
|
+
`Warning: Unmatched literal/templating states. Final state: ${currentState}, Remaining stack: ${stateStack.join(", ")}`,
|
|
370
|
+
);
|
|
371
|
+
}
|
|
372
|
+
return jsCode;
|
|
373
|
+
}
|
package/src/index.js
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* prettier-plugin-noshift.js
|
|
3
|
+
*
|
|
4
|
+
* Prettier plugin for NoShift.js (.nsjs) files.
|
|
5
|
+
*
|
|
6
|
+
* Pipeline:
|
|
7
|
+
* 1. nsjs → js (convertNsjsToJs)
|
|
8
|
+
* 2. js → formatted js (Prettier babel parser)
|
|
9
|
+
* 3. formatted js → nsjs (convertJsToNsjs)
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import * as prettier from "prettier";
|
|
13
|
+
import { convertNsjsToJs } from "./convert.js";
|
|
14
|
+
import { convertJsToNsjs } from "./reverse.js";
|
|
15
|
+
|
|
16
|
+
// ======
|
|
17
|
+
// Language 定義
|
|
18
|
+
// ======
|
|
19
|
+
export const languages = [
|
|
20
|
+
{
|
|
21
|
+
name: "NoShift.js",
|
|
22
|
+
parsers: ["noshift"],
|
|
23
|
+
extensions: [".nsjs"],
|
|
24
|
+
vscodeLanguageIds: ["noshift"],
|
|
25
|
+
},
|
|
26
|
+
];
|
|
27
|
+
|
|
28
|
+
// ======
|
|
29
|
+
// 汎用のフォーマットオプションだけ転送する
|
|
30
|
+
// ======
|
|
31
|
+
const FORWARD_OPTIONS = [
|
|
32
|
+
"tabWidth",
|
|
33
|
+
"useTabs",
|
|
34
|
+
"semi",
|
|
35
|
+
"singleQuote",
|
|
36
|
+
"trailingComma",
|
|
37
|
+
"bracketSpacing",
|
|
38
|
+
"arrowParens",
|
|
39
|
+
"printWidth",
|
|
40
|
+
"endOfLine",
|
|
41
|
+
"quoteProps",
|
|
42
|
+
"jsxSingleQuote",
|
|
43
|
+
"singleAttributePerLine",
|
|
44
|
+
];
|
|
45
|
+
|
|
46
|
+
function pickFormatOptions(options) {
|
|
47
|
+
const picked = {};
|
|
48
|
+
for (const key of FORWARD_OPTIONS) {
|
|
49
|
+
if (key in options) {
|
|
50
|
+
picked[key] = options[key];
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
return picked;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// ======
|
|
57
|
+
// Parser 定義
|
|
58
|
+
// ======
|
|
59
|
+
export const parsers = {
|
|
60
|
+
noshift: {
|
|
61
|
+
parse: async (text, options) => {
|
|
62
|
+
// 1) nsjs → js
|
|
63
|
+
const jsCode = convertNsjsToJs(text);
|
|
64
|
+
|
|
65
|
+
// 2) Prettier で js をフォーマット
|
|
66
|
+
const fmtOptions = {
|
|
67
|
+
...pickFormatOptions(options),
|
|
68
|
+
parser: "babel",
|
|
69
|
+
plugins: [],
|
|
70
|
+
};
|
|
71
|
+
const formatted = await prettier.format(jsCode, fmtOptions);
|
|
72
|
+
|
|
73
|
+
// 3) フォーマット済み js → nsjs
|
|
74
|
+
const nsjsFormatted = convertJsToNsjs(formatted.trimEnd());
|
|
75
|
+
|
|
76
|
+
return {
|
|
77
|
+
type: "NoshiftProgram",
|
|
78
|
+
body: nsjsFormatted,
|
|
79
|
+
};
|
|
80
|
+
},
|
|
81
|
+
astFormat: "noshift-ast",
|
|
82
|
+
locStart: () => 0,
|
|
83
|
+
locEnd: (node) => (node.body ? node.body.length : 0),
|
|
84
|
+
},
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
// ======
|
|
88
|
+
// Printer 定義
|
|
89
|
+
// ======
|
|
90
|
+
export const printers = {
|
|
91
|
+
"noshift-ast": {
|
|
92
|
+
print: (path) => {
|
|
93
|
+
const node = path.getValue();
|
|
94
|
+
return node.body + "\n";
|
|
95
|
+
},
|
|
96
|
+
},
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
export default { languages, parsers, printers };
|
package/src/reverse.js
ADDED
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* JavaScript → NoShift.js 逆変換器
|
|
3
|
+
* Prettierでフォーマット済みのJSコードをNoShift.js構文に変換する。
|
|
4
|
+
*
|
|
5
|
+
* 変換ルール:
|
|
6
|
+
* - コード内のShift記号 → ^X 表記
|
|
7
|
+
* - 大文字 → ^3 + 小文字
|
|
8
|
+
* - 文字列の区切り: " → ^2, ' → ^7, ` → ^@
|
|
9
|
+
* - 文字列内容はそのまま(^3 による大文字化は保持)
|
|
10
|
+
* - ブロックコメント: /* → /^:, */ → ^:/
|
|
11
|
+
* - テンプレート式展開: ${ → ^4^[, } → ^]
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
// JavaScript 記号 → NoShift.js 表記
|
|
15
|
+
const jsToNsMap = {
|
|
16
|
+
"!": "^1",
|
|
17
|
+
'"': "^2",
|
|
18
|
+
$: "^4",
|
|
19
|
+
"%": "^5",
|
|
20
|
+
"&": "^6",
|
|
21
|
+
"'": "^7",
|
|
22
|
+
"(": "^8",
|
|
23
|
+
")": "^9",
|
|
24
|
+
"=": "^-",
|
|
25
|
+
"~": "^^",
|
|
26
|
+
"|": "^\\",
|
|
27
|
+
"`": "^@",
|
|
28
|
+
"{": "^[",
|
|
29
|
+
"}": "^]",
|
|
30
|
+
"+": "^;",
|
|
31
|
+
"*": "^:",
|
|
32
|
+
"<": "^,",
|
|
33
|
+
">": "^.",
|
|
34
|
+
"?": "^/",
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
const STATE = {
|
|
38
|
+
NORMAL: "NORMAL",
|
|
39
|
+
IN_DQ_STRING: "IN_DQ_STRING",
|
|
40
|
+
IN_SQ_STRING: "IN_SQ_STRING",
|
|
41
|
+
IN_TEMPLATE: "IN_TEMPLATE",
|
|
42
|
+
IN_TEMPLATE_EXPR: "IN_TEMPLATE_EXPR",
|
|
43
|
+
IN_LINE_COMMENT: "IN_LINE_COMMENT",
|
|
44
|
+
IN_BLOCK_COMMENT: "IN_BLOCK_COMMENT",
|
|
45
|
+
IN_REGEX: "IN_REGEX",
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* フォーマット済みJSをNoShift.js構文に変換する。
|
|
50
|
+
* @param {string} jsCode - Prettierでフォーマット済みのJSコード
|
|
51
|
+
* @param {{ capitalizeInStrings?: boolean }} [options={}]
|
|
52
|
+
* @returns {string} NoShift.jsコード
|
|
53
|
+
*/
|
|
54
|
+
export function convertJsToNsjs(jsCode, options = {}) {
|
|
55
|
+
const capitalizeInStrings = options.capitalizeInStrings !== false;
|
|
56
|
+
let nsCode = "";
|
|
57
|
+
let i = 0;
|
|
58
|
+
const len = jsCode.length;
|
|
59
|
+
|
|
60
|
+
let currentState = STATE.NORMAL;
|
|
61
|
+
const stateStack = [];
|
|
62
|
+
let templateDepth = 0;
|
|
63
|
+
// テンプレートリテラルのネスト深度を追跡するスタック
|
|
64
|
+
const templateBraceDepth = [];
|
|
65
|
+
|
|
66
|
+
while (i < len) {
|
|
67
|
+
const ch = jsCode[i];
|
|
68
|
+
const next = jsCode[i + 1];
|
|
69
|
+
|
|
70
|
+
// ======
|
|
71
|
+
// 行コメント
|
|
72
|
+
// ======
|
|
73
|
+
if (currentState === STATE.IN_LINE_COMMENT) {
|
|
74
|
+
if (ch === "\n") {
|
|
75
|
+
nsCode += "\n";
|
|
76
|
+
currentState = stateStack.pop();
|
|
77
|
+
} else {
|
|
78
|
+
nsCode += ch;
|
|
79
|
+
}
|
|
80
|
+
i++;
|
|
81
|
+
continue;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// ======
|
|
85
|
+
// ブロックコメント
|
|
86
|
+
// ======
|
|
87
|
+
if (currentState === STATE.IN_BLOCK_COMMENT) {
|
|
88
|
+
if (ch === "*" && next === "/") {
|
|
89
|
+
nsCode += "^:/";
|
|
90
|
+
i += 2;
|
|
91
|
+
currentState = stateStack.pop();
|
|
92
|
+
} else {
|
|
93
|
+
nsCode += ch;
|
|
94
|
+
i++;
|
|
95
|
+
}
|
|
96
|
+
continue;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// ======
|
|
100
|
+
// ダブルクォート文字列
|
|
101
|
+
// ======
|
|
102
|
+
if (currentState === STATE.IN_DQ_STRING) {
|
|
103
|
+
if (ch === "\\" && i + 1 < len) {
|
|
104
|
+
// エスケープシーケンスはそのまま
|
|
105
|
+
nsCode += ch + next;
|
|
106
|
+
i += 2;
|
|
107
|
+
} else if (ch === '"') {
|
|
108
|
+
nsCode += "^2";
|
|
109
|
+
currentState = stateStack.pop();
|
|
110
|
+
i++;
|
|
111
|
+
} else if (capitalizeInStrings && /[A-Z]/.test(ch)) {
|
|
112
|
+
nsCode += "^3" + ch.toLowerCase();
|
|
113
|
+
i++;
|
|
114
|
+
} else {
|
|
115
|
+
nsCode += ch;
|
|
116
|
+
i++;
|
|
117
|
+
}
|
|
118
|
+
continue;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// ======
|
|
122
|
+
// シングルクォート文字列
|
|
123
|
+
// ======
|
|
124
|
+
if (currentState === STATE.IN_SQ_STRING) {
|
|
125
|
+
if (ch === "\\" && i + 1 < len) {
|
|
126
|
+
nsCode += ch + next;
|
|
127
|
+
i += 2;
|
|
128
|
+
} else if (ch === "'") {
|
|
129
|
+
nsCode += "^7";
|
|
130
|
+
currentState = stateStack.pop();
|
|
131
|
+
i++;
|
|
132
|
+
} else if (capitalizeInStrings && /[A-Z]/.test(ch)) {
|
|
133
|
+
nsCode += "^3" + ch.toLowerCase();
|
|
134
|
+
i++;
|
|
135
|
+
} else {
|
|
136
|
+
nsCode += ch;
|
|
137
|
+
i++;
|
|
138
|
+
}
|
|
139
|
+
continue;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// ======
|
|
143
|
+
// テンプレートリテラル
|
|
144
|
+
// ======
|
|
145
|
+
if (currentState === STATE.IN_TEMPLATE) {
|
|
146
|
+
if (ch === "\\" && i + 1 < len) {
|
|
147
|
+
nsCode += ch + next;
|
|
148
|
+
i += 2;
|
|
149
|
+
} else if (ch === "$" && next === "{") {
|
|
150
|
+
nsCode += "^4^[";
|
|
151
|
+
i += 2;
|
|
152
|
+
stateStack.push(currentState);
|
|
153
|
+
currentState = STATE.IN_TEMPLATE_EXPR;
|
|
154
|
+
templateBraceDepth.push(0);
|
|
155
|
+
} else if (ch === "`") {
|
|
156
|
+
nsCode += "^@";
|
|
157
|
+
currentState = stateStack.pop();
|
|
158
|
+
i++;
|
|
159
|
+
} else if (capitalizeInStrings && /[A-Z]/.test(ch)) {
|
|
160
|
+
nsCode += "^3" + ch.toLowerCase();
|
|
161
|
+
i++;
|
|
162
|
+
} else {
|
|
163
|
+
nsCode += ch;
|
|
164
|
+
i++;
|
|
165
|
+
}
|
|
166
|
+
continue;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// ======
|
|
170
|
+
// テンプレート式内 (${ ... })
|
|
171
|
+
// ======
|
|
172
|
+
if (currentState === STATE.IN_TEMPLATE_EXPR) {
|
|
173
|
+
// テンプレート式内の { } のネスト追跡
|
|
174
|
+
if (ch === "{") {
|
|
175
|
+
templateBraceDepth[templateBraceDepth.length - 1]++;
|
|
176
|
+
nsCode += "^[";
|
|
177
|
+
i++;
|
|
178
|
+
continue;
|
|
179
|
+
}
|
|
180
|
+
if (ch === "}") {
|
|
181
|
+
const depth = templateBraceDepth[templateBraceDepth.length - 1];
|
|
182
|
+
if (depth === 0) {
|
|
183
|
+
// テンプレート式の終了
|
|
184
|
+
templateBraceDepth.pop();
|
|
185
|
+
nsCode += "^]";
|
|
186
|
+
currentState = stateStack.pop();
|
|
187
|
+
i++;
|
|
188
|
+
continue;
|
|
189
|
+
} else {
|
|
190
|
+
templateBraceDepth[templateBraceDepth.length - 1]--;
|
|
191
|
+
nsCode += "^]";
|
|
192
|
+
i++;
|
|
193
|
+
continue;
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
// テンプレート式内は通常コードと同じ変換を適用
|
|
197
|
+
// (以下のNORMAL処理にフォールスルー)
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// ======
|
|
201
|
+
// NORMAL + IN_TEMPLATE_EXPR 共通処理
|
|
202
|
+
// ======
|
|
203
|
+
if (
|
|
204
|
+
currentState === STATE.NORMAL ||
|
|
205
|
+
currentState === STATE.IN_TEMPLATE_EXPR
|
|
206
|
+
) {
|
|
207
|
+
// 行コメント
|
|
208
|
+
if (ch === "/" && next === "/") {
|
|
209
|
+
nsCode += "//";
|
|
210
|
+
i += 2;
|
|
211
|
+
stateStack.push(currentState);
|
|
212
|
+
currentState = STATE.IN_LINE_COMMENT;
|
|
213
|
+
continue;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// ブロックコメント
|
|
217
|
+
if (ch === "/" && next === "*") {
|
|
218
|
+
nsCode += "/^:";
|
|
219
|
+
i += 2;
|
|
220
|
+
stateStack.push(currentState);
|
|
221
|
+
currentState = STATE.IN_BLOCK_COMMENT;
|
|
222
|
+
continue;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// ダブルクォート文字列開始
|
|
226
|
+
if (ch === '"') {
|
|
227
|
+
nsCode += "^2";
|
|
228
|
+
i++;
|
|
229
|
+
stateStack.push(currentState);
|
|
230
|
+
currentState = STATE.IN_DQ_STRING;
|
|
231
|
+
continue;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// シングルクォート文字列開始
|
|
235
|
+
if (ch === "'") {
|
|
236
|
+
nsCode += "^7";
|
|
237
|
+
i++;
|
|
238
|
+
stateStack.push(currentState);
|
|
239
|
+
currentState = STATE.IN_SQ_STRING;
|
|
240
|
+
continue;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// テンプレートリテラル開始
|
|
244
|
+
if (ch === "`") {
|
|
245
|
+
nsCode += "^@";
|
|
246
|
+
i++;
|
|
247
|
+
stateStack.push(currentState);
|
|
248
|
+
currentState = STATE.IN_TEMPLATE;
|
|
249
|
+
continue;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
// 大文字
|
|
253
|
+
if (/[A-Z]/.test(ch)) {
|
|
254
|
+
nsCode += "^3" + ch.toLowerCase();
|
|
255
|
+
i++;
|
|
256
|
+
continue;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
// Shift 記号
|
|
260
|
+
if (jsToNsMap[ch]) {
|
|
261
|
+
nsCode += jsToNsMap[ch];
|
|
262
|
+
i++;
|
|
263
|
+
continue;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
// その他(小文字、数字、スペース、改行など)
|
|
267
|
+
nsCode += ch;
|
|
268
|
+
i++;
|
|
269
|
+
continue;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
// フォールバック
|
|
273
|
+
nsCode += ch;
|
|
274
|
+
i++;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
return nsCode;
|
|
278
|
+
}
|