noshift.js 0.3.0 → 0.4.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-ja.md CHANGED
@@ -1,10 +1,21 @@
1
- # noshift.js
1
+ [![npm](https://img.shields.io/npm/v/noshift.js)](https://www.npmjs.com/package/noshift.js) [![license](https://img.shields.io/badge/license-MIT-blue.svg)](./LICENSE)
2
+
3
+ # NoShift.js
4
+
5
+ <div align="center">
6
+ <img src="https://raw.githubusercontent.com/otoneko1102/NoShift.js/refs/heads/main/icon.png" alt="noshift.js" width="128" height="128">
7
+ </div>
8
+
9
+ <div align="center">
10
+
11
+ [English](./README.md) | **日本語**
12
+
13
+ </div>
2
14
 
3
15
  > Shift キーを押さずに JavaScript を書ける Joke 言語
4
16
 
5
- [![npm](https://img.shields.io/npm/v/noshift.js)](https://www.npmjs.com/package/noshift.js)
6
- [![license](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
7
- [![en](https://img.shields.io/badge/lang-English_version-blue)](README.md)
17
+ 記号 (`!`, `"`, `(`, `)`, `{`, `}` …) を入力するときに Shift を押すのが面倒なので、Shift を押さずに JavaScript が書けるようにした Joke 言語です。
18
+ `^` プレフィックスのシーケンスで Shift が必要な記号を表現し、`.nsjs` ファイルは `nsc` CLI を使って JavaScript にコンパイルされます。
8
19
 
9
20
  ---
10
21
 
@@ -19,11 +30,11 @@ npm install -g noshift.js@latest
19
30
  ## はじめに
20
31
 
21
32
  ```bash
22
- # インタラクティブなプロジェクトスキャフォールド
23
- nsc create
33
+ # 新しいプロジェクトを作成
34
+ nsc create my-project
24
35
 
25
36
  # または、現在のディレクトリに nsjsconfig.json だけを作成
26
- nsc --init
37
+ nsc init
27
38
  ```
28
39
 
29
40
  ---
@@ -32,37 +43,41 @@ nsc --init
32
43
 
33
44
  `nsc` は TypeScript の `tsc` に似た使い心地を目指しています。
34
45
 
35
- | コマンド | 説明 |
36
- |---|---|
37
- | `nsc` | `nsjsconfig.json` を使って `.nsjs` → `.js` にコンパイル |
38
- | `nsc -w` / `nsc --watch` | 変更を監視して自動的に再コンパイル |
39
- | `nsc --init` | 現在のディレクトリに `nsjsconfig.json` を作成 |
40
- | `nsc --clean` | 出力ディレクトリ (`outDir`) を削除 |
41
- | `nsc run <file>` | `.nsjs` ファイルを直接実行 |
42
- | `nsc create [name]` | インタラクティブに新しいプロジェクトを作成 |
43
- | `nsc -V` | バージョンを表示 |
44
- | `nsc -h` | ヘルプを表示 |
46
+ | コマンド | エイリアス | 説明 |
47
+ |---|---|---|
48
+ | `nsc` | | `nsjsconfig.json` を使って `.nsjs` → `.js` にコンパイル |
49
+ | `nsc watch` | `nsc -w` `nsc --watch` | 変更を監視して自動的に再コンパイル |
50
+ | `nsc init` | `nsc --init` | 現在のディレクトリに `nsjsconfig.json` を作成 |
51
+ | `nsc clean` | `nsc --clean` | 出力ディレクトリ (`outdir`) を削除 |
52
+ | `nsc run <file>` | `nsc -r <file>` `nsc --run <file>` | `.nsjs` ファイルを直接実行 |
53
+ | `nsc create [name]` | `nsc --create [name]` | 新しいプロジェクトを作成(`--no-prettier` で Prettier スキップ) |
54
+ | `nsc version` | `nsc -v` `nsc --version` | バージョンを表示 |
55
+ | `nsc help` | `nsc -h` `nsc --help` | ヘルプを表示 |
45
56
 
46
57
  ---
47
58
 
48
59
  ## nsjsconfig.json
49
60
 
50
61
  プロジェクトルートに `nsjsconfig.json` を置くとコンパイル設定を行えます。
51
- `nsc --init` または `nsc create` で自動生成されます。
62
+ `nsc init` または `nsc create` で自動生成されます。
52
63
 
53
64
  ```json
54
65
  {
55
- "compilerOptions": {
56
- "rootDir": "src",
57
- "outDir": "dist"
66
+ "compileroptions": {
67
+ "rootdir": "src",
68
+ "outdir": "dist",
69
+ "warnuppercase": true,
70
+ "capitalizeinstrings": true
58
71
  }
59
72
  }
60
73
  ```
61
74
 
62
75
  | オプション | デフォルト | 説明 |
63
76
  |---|---|---|
64
- | `compilerOptions.rootDir` | `"src"` | ソースディレクトリ |
65
- | `compilerOptions.outDir` | `"dist"` | 出力ディレクトリ |
77
+ | `compileroptions.rootdir` | `"src"` | ソースディレクトリ |
78
+ | `compileroptions.outdir` | `"dist"` | 出力ディレクトリ |
79
+ | `compileroptions.warnuppercase` | `true` | ソースコード内の大文字を警告 |
80
+ | `compileroptions.capitalizeinstrings` | `true` | 文字列リテラル内で `^3` 大文字化修飾子を有効にする |
66
81
 
67
82
  ---
68
83
 
@@ -76,14 +91,14 @@ nsc --init
76
91
  |:-------:|:--:|---|:-------:|:--:|
77
92
  | `^1` | `!` | | `^^` | `~` |
78
93
  | `^2` | `"` | | `^\` | `\|` |
79
- | `^4` | `$` | | `^@` | `` ` `` |
80
- | `^5` | `%` | | `^[` | `{` |
81
- | `^6` | `&` | | `^]` | `}` |
82
- | `^7` | `'` | | `^;` | `+` |
83
- | `^8` | `(` | | `^:` | `*` |
84
- | `^9` | `)` | | `^,` | `<` |
85
- | `^-` | `=` | | `^.` | `>` |
86
- | | | | `^/` | `?` |
94
+ | `^3x` | `X`(大文字化) | | `^@` | `` ` `` |
95
+ | `^4` | `$` | | `^[` | `{` |
96
+ | `^5` | `%` | | `^]` | `}` |
97
+ | `^6` | `&` | | `^;` | `+` |
98
+ | `^7` | `'` | | `^:` | `*` |
99
+ | `^8` | `(` | | `^,` | `<` |
100
+ | `^9` | `)` | | `^.` | `>` |
101
+ | `^-` | `=` | | `^/` | `?` |
87
102
 
88
103
  テンプレート式: `^4^[` → `${`
89
104
 
@@ -101,6 +116,44 @@ console.log^8^2Hello, World!^2^9;
101
116
  console.log("Hello, World!");
102
117
  ```
103
118
 
119
+ ### 大文字化修飾子
120
+
121
+ `^3` は次の文字を大文字にします:
122
+
123
+ ```nsjs
124
+ class ^3animal ^[
125
+ ^]
126
+ ```
127
+
128
+ ```js
129
+ class Animal {
130
+ }
131
+ ```
132
+
133
+ ### コメント
134
+
135
+ ```nsjs
136
+ // 行コメント
137
+
138
+ /^: ブロックコメント ^:/
139
+
140
+ /^:
141
+ 複数行の
142
+ ブロックコメント
143
+ ^:/
144
+ ```
145
+
146
+ ```js
147
+ // 行コメント
148
+
149
+ /* ブロックコメント */
150
+
151
+ /*
152
+ 複数行の
153
+ ブロックコメント
154
+ */
155
+ ```
156
+
104
157
  ### 変数とアロー関数
105
158
 
106
159
  ```nsjs
@@ -165,7 +218,7 @@ const arr = [1, 2, 3];
165
218
  ### クラス
166
219
 
167
220
  ```nsjs
168
- class Animal ^[
221
+ class ^3animal ^[
169
222
  constructor^8name^9 ^[
170
223
  this.name ^- name;
171
224
  ^]
@@ -175,7 +228,7 @@ class Animal ^[
175
228
  ^]
176
229
  ^]
177
230
 
178
- const dog ^- new Animal^8^2Dog^2^9;
231
+ const dog ^- new ^3animal^8^2Dog^2^9;
179
232
  dog.speak^8^9;
180
233
  ```
181
234
 
package/README.md CHANGED
@@ -1,10 +1,22 @@
1
- # noshift.js
1
+ [![npm](https://img.shields.io/npm/v/noshift.js)](https://www.npmjs.com/package/noshift.js) [![license](https://img.shields.io/badge/license-MIT-blue.svg)](./LICENSE)
2
+
3
+ # NoShift.js
4
+
5
+ <div align="center">
6
+ <img src="https://raw.githubusercontent.com/otoneko1102/NoShift.js/refs/heads/main/icon.png" alt="noshift.js" width="128" height="128">
7
+ </div>
8
+
9
+ <div align="center">
10
+
11
+ **English** | [日本語](./README-ja.md)
12
+
13
+ </div>
2
14
 
3
15
  > A joke language that lets you write JavaScript without pressing the Shift key.
4
16
 
5
- [![npm](https://img.shields.io/npm/v/noshift.js)](https://www.npmjs.com/package/noshift.js)
6
- [![license](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
7
- [![ja](https://img.shields.io/badge/lang-日本語_バージョン-red)](README-ja.md)
17
+ Typing shifted symbols (`!`, `"`, `(`, `)`, `{`, `}` …) is tiring.
18
+ **NoShift.js** replaces every shift-required symbol with a `^`-prefixed sequence, so you can write JavaScript using only unshifted keys.
19
+ `.nsjs` files compile directly to plain JavaScript via the `nsc` CLI.
8
20
 
9
21
  ---
10
22
 
@@ -19,11 +31,11 @@ npm install -g noshift.js@latest
19
31
  ## Getting Started
20
32
 
21
33
  ```bash
22
- # Interactive full project scaffold
23
- nsc create
34
+ # Create a new project
35
+ nsc create my-project
24
36
 
25
37
  # Or initialize only a nsjsconfig.json in the current directory
26
- nsc --init
38
+ nsc init
27
39
  ```
28
40
 
29
41
  ---
@@ -32,37 +44,41 @@ nsc --init
32
44
 
33
45
  `nsc` is designed to feel like TypeScript's `tsc`.
34
46
 
35
- | Command | Description |
36
- |---|---|
37
- | `nsc` | Compile `.nsjs` → `.js` using `nsjsconfig.json` |
38
- | `nsc -w` / `nsc --watch` | Watch for changes and recompile automatically |
39
- | `nsc --init` | Create `nsjsconfig.json` in the current directory |
40
- | `nsc --clean` | Delete the output directory (`outDir`) |
41
- | `nsc run <file>` | Run a `.nsjs` file directly |
42
- | `nsc create [name]` | Scaffold a new project interactively |
43
- | `nsc -V` | Show version |
44
- | `nsc -h` | Show help |
47
+ | Command | Alias | Description |
48
+ |---|---|---|
49
+ | `nsc` | | Compile `.nsjs` → `.js` using `nsjsconfig.json` |
50
+ | `nsc watch` | `nsc -w` `nsc --watch` | Watch for changes and recompile automatically |
51
+ | `nsc init` | `nsc --init` | Create `nsjsconfig.json` in the current directory |
52
+ | `nsc clean` | `nsc --clean` | Delete the output directory (`outdir`) |
53
+ | `nsc run <file>` | `nsc -r <file>` `nsc --run <file>` | Run a `.nsjs` file directly |
54
+ | `nsc create [name]` | `nsc --create [name]` | Scaffold a new project (`--no-prettier` to skip Prettier) |
55
+ | `nsc version` | `nsc -v` `nsc --version` | Show version |
56
+ | `nsc help` | `nsc -h` `nsc --help` | Show help |
45
57
 
46
58
  ---
47
59
 
48
60
  ## nsjsconfig.json
49
61
 
50
62
  Place a `nsjsconfig.json` at the project root to configure compilation.
51
- Generated automatically by `nsc --init` or `nsc create`.
63
+ Generated automatically by `nsc init` or `nsc create`.
52
64
 
53
65
  ```json
54
66
  {
55
- "compilerOptions": {
56
- "rootDir": "src",
57
- "outDir": "dist"
67
+ "compileroptions": {
68
+ "rootdir": "src",
69
+ "outdir": "dist",
70
+ "warnuppercase": true,
71
+ "capitalizeinstrings": true
58
72
  }
59
73
  }
60
74
  ```
61
75
 
62
76
  | Option | Default | Description |
63
77
  |---|---|---|
64
- | `compilerOptions.rootDir` | `"src"` | Source directory |
65
- | `compilerOptions.outDir` | `"dist"` | Output directory |
78
+ | `compileroptions.rootdir` | `"src"` | Source directory |
79
+ | `compileroptions.outdir` | `"dist"` | Output directory |
80
+ | `compileroptions.warnuppercase` | `true` | Warn about uppercase characters in source code |
81
+ | `compileroptions.capitalizeinstrings` | `true` | Enable `^3` capitalize modifier inside string literals |
66
82
 
67
83
  ---
68
84
 
@@ -76,14 +92,14 @@ Generated automatically by `nsc --init` or `nsc create`.
76
92
  |:-------:|:--:|---|:-------:|:--:|
77
93
  | `^1` | `!` | | `^^` | `~` |
78
94
  | `^2` | `"` | | `^\` | `\|` |
79
- | `^4` | `$` | | `^@` | `` ` `` |
80
- | `^5` | `%` | | `^[` | `{` |
81
- | `^6` | `&` | | `^]` | `}` |
82
- | `^7` | `'` | | `^;` | `+` |
83
- | `^8` | `(` | | `^:` | `*` |
84
- | `^9` | `)` | | `^,` | `<` |
85
- | `^-` | `=` | | `^.` | `>` |
86
- | | | | `^/` | `?` |
95
+ | `^3x` | `X` (capitalize) | | `^@` | `` ` `` |
96
+ | `^4` | `$` | | `^[` | `{` |
97
+ | `^5` | `%` | | `^]` | `}` |
98
+ | `^6` | `&` | | `^;` | `+` |
99
+ | `^7` | `'` | | `^:` | `*` |
100
+ | `^8` | `(` | | `^,` | `<` |
101
+ | `^9` | `)` | | `^.` | `>` |
102
+ | `^-` | `=` | | `^/` | `?` |
87
103
 
88
104
  Template expression: `^4^[` → `${`
89
105
 
@@ -101,6 +117,44 @@ console.log^8^2Hello, World!^2^9;
101
117
  console.log("Hello, World!");
102
118
  ```
103
119
 
120
+ ### Capitalize Modifier
121
+
122
+ `^3` capitalizes the next character:
123
+
124
+ ```nsjs
125
+ class ^3animal ^[
126
+ ^]
127
+ ```
128
+
129
+ ```js
130
+ class Animal {
131
+ }
132
+ ```
133
+
134
+ ### Comments
135
+
136
+ ```nsjs
137
+ // line comment
138
+
139
+ /^: block comment ^:/
140
+
141
+ /^:
142
+ multi-line
143
+ block comment
144
+ ^:/
145
+ ```
146
+
147
+ ```js
148
+ // line comment
149
+
150
+ /* block comment */
151
+
152
+ /*
153
+ multi-line
154
+ block comment
155
+ */
156
+ ```
157
+
104
158
  ### Variables & Arrow Functions
105
159
 
106
160
  ```nsjs
@@ -165,7 +219,7 @@ const arr = [1, 2, 3];
165
219
  ### Classes
166
220
 
167
221
  ```nsjs
168
- class Animal ^[
222
+ class ^3animal ^[
169
223
  constructor^8name^9 ^[
170
224
  this.name ^- name;
171
225
  ^]
@@ -175,7 +229,7 @@ class Animal ^[
175
229
  ^]
176
230
  ^]
177
231
 
178
- const dog ^- new Animal^8^2Dog^2^9;
232
+ const dog ^- new ^3animal^8^2Dog^2^9;
179
233
  dog.speak^8^9;
180
234
  ```
181
235
 
package/bin/cli.js CHANGED
@@ -10,32 +10,52 @@ const pkg = JSON.parse(
10
10
  readFileSync(path.join(__dirname, "../package.json"), "utf-8"),
11
11
  );
12
12
 
13
+ const DOCS_URL = "https://noshift.js.org/";
14
+
13
15
  const program = new Command();
14
16
 
15
- // nsc [options] ← デフォルト動作: compile
16
17
  program
17
18
  .name("nsc")
18
19
  .description("NoShift.js compiler")
19
- .version(pkg.version, '-v, --version', 'output the version number')
20
+ .version(pkg.version, "-v, --version", "output the version number")
20
21
  .option("-w, --watch", "Watch for file changes and recompile")
21
22
  .option("--init", "Create a nsjsconfig.json in the current directory")
22
- .option("--clean", "Delete the output directory (outDir)")
23
+ .option("--clean", "Delete the output directory (outdir)")
24
+ .option("-r, --run <file>", "Run a .nsjs file directly")
25
+ .option("--create [name]", "Scaffold a new NoShift.js project")
26
+ .addHelpText("after", `\nDocumentation: ${DOCS_URL}`)
23
27
  .action(async (options) => {
24
- if (options.init) {
28
+ if (options.watch) {
29
+ const { default: dev } = await import("../commands/dev.js");
30
+ await dev();
31
+ } else if (options.init) {
25
32
  const { default: init } = await import("../commands/init.js");
26
33
  await init();
27
34
  } else if (options.clean) {
28
35
  const { default: clean } = await import("../commands/clean.js");
29
36
  await clean();
30
- } else if (options.watch) {
31
- const { default: dev } = await import("../commands/dev.js");
32
- await dev();
37
+ } else if (options.run) {
38
+ const { default: run } = await import("../commands/run.js");
39
+ await run(options.run);
40
+ } else if (options.create !== undefined) {
41
+ const { default: create } = await import("../commands/create.js");
42
+ await create(options.create || undefined);
33
43
  } else {
34
44
  const { default: compile } = await import("../commands/compile.js");
35
45
  await compile();
36
46
  }
37
47
  });
38
48
 
49
+ // nsc watch
50
+ program
51
+ .command("watch")
52
+ .alias("w")
53
+ .description("Watch for file changes and recompile")
54
+ .action(async () => {
55
+ const { default: dev } = await import("../commands/dev.js");
56
+ await dev();
57
+ });
58
+
39
59
  // nsc run <file>
40
60
  program
41
61
  .command("run <file>")
@@ -45,13 +65,49 @@ program
45
65
  await run(file);
46
66
  });
47
67
 
48
- // nsc create [name] ← 対話式プロジェクト作成
68
+ // nsc create [name]
49
69
  program
50
70
  .command("create [name]")
51
- .description("Scaffold a new NoShift.js project interactively")
52
- .action(async (name) => {
71
+ .description("Scaffold a new NoShift.js project")
72
+ .option("--prettier", "Include Prettier (default)")
73
+ .option("--no-prettier", "Skip Prettier setup")
74
+ .action(async (name, options) => {
53
75
  const { default: create } = await import("../commands/create.js");
54
- await create(name);
76
+ await create(name, options);
77
+ });
78
+
79
+ // nsc init
80
+ program
81
+ .command("init")
82
+ .description("Create a nsjsconfig.json in the current directory")
83
+ .action(async () => {
84
+ const { default: init } = await import("../commands/init.js");
85
+ await init();
86
+ });
87
+
88
+ // nsc clean
89
+ program
90
+ .command("clean")
91
+ .description("Delete the output directory (outdir)")
92
+ .action(async () => {
93
+ const { default: clean } = await import("../commands/clean.js");
94
+ await clean();
95
+ });
96
+
97
+ // nsc version
98
+ program
99
+ .command("version")
100
+ .description("Display the current version")
101
+ .action(() => {
102
+ console.log(pkg.version);
103
+ });
104
+
105
+ // nsc help
106
+ program
107
+ .command("help")
108
+ .description("Show help information")
109
+ .action(() => {
110
+ program.outputHelp();
55
111
  });
56
112
 
57
113
  program.parse();
package/commands/clean.js CHANGED
@@ -17,17 +17,17 @@ export default async function clean() {
17
17
  process.exit(1);
18
18
  }
19
19
 
20
- const outDir = path.resolve(cwd, config.compilerOptions.outDir);
20
+ const outDir = path.resolve(cwd, config.compileroptions.outdir);
21
21
 
22
22
  try {
23
23
  await access(outDir);
24
24
  } catch {
25
25
  logger.info(
26
- `Nothing to clean (${logger.highlight(config.compilerOptions.outDir)} does not exist).`,
26
+ `Nothing to clean (${logger.highlight(config.compileroptions.outdir)} does not exist).`,
27
27
  );
28
28
  return;
29
29
  }
30
30
 
31
31
  await rm(outDir, { recursive: true, force: true });
32
- logger.success(`Deleted ${logger.highlight(config.compilerOptions.outDir)}`);
32
+ logger.success(`Deleted ${logger.highlight(config.compileroptions.outdir)}`);
33
33
  }
@@ -1,6 +1,6 @@
1
1
  import { promises as fs } from "fs";
2
2
  import path from "path";
3
- import convert from "../src/convert.js";
3
+ import convert, { checkUppercaseWarnings } from "../src/convert.js";
4
4
  import { loadConfig } from "../src/config.js";
5
5
  import { handleSigint } from "../src/signal-handler.js";
6
6
  import * as logger from "../src/logger.js";
@@ -39,15 +39,15 @@ export default async function compile() {
39
39
  process.exit(1);
40
40
  }
41
41
 
42
- const rootDir = path.resolve(cwd, config.compilerOptions.rootDir);
43
- const outDir = path.resolve(cwd, config.compilerOptions.outDir);
42
+ const rootDir = path.resolve(cwd, config.compileroptions.rootdir);
43
+ const outDir = path.resolve(cwd, config.compileroptions.outdir);
44
44
 
45
45
  const files = await findNsjsFiles(rootDir);
46
46
 
47
47
  if (files === null) {
48
48
  logger.errorCode(
49
49
  "NS0",
50
- `rootDir '${config.compilerOptions.rootDir}' not found.`,
50
+ `rootdir '${config.compileroptions.rootdir}' not found.`,
51
51
  );
52
52
  process.exit(1);
53
53
  }
@@ -61,6 +61,11 @@ export default async function compile() {
61
61
 
62
62
  let compiled = 0;
63
63
  let errors = 0;
64
+ let totalWarnings = 0;
65
+ const warnUppercase = config.compileroptions.warnuppercase !== false;
66
+ const convertOptions = {
67
+ capitalizeInStrings: config.compileroptions.capitalizeinstrings !== false,
68
+ };
64
69
 
65
70
  for (const file of files) {
66
71
  const relative = path.relative(rootDir, file);
@@ -68,7 +73,19 @@ export default async function compile() {
68
73
 
69
74
  try {
70
75
  const code = await fs.readFile(file, "utf-8");
71
- const js = convert(code);
76
+
77
+ // 大文字警告チェック
78
+ if (warnUppercase) {
79
+ const warnings = checkUppercaseWarnings(code);
80
+ for (const w of warnings) {
81
+ logger.warn(
82
+ `${relative.replace(/\\/g, "/")}:${w.line}:${w.column} - ${w.message}`,
83
+ );
84
+ totalWarnings++;
85
+ }
86
+ }
87
+
88
+ const js = convert(code, convertOptions);
72
89
 
73
90
  await fs.mkdir(path.dirname(destPath), { recursive: true });
74
91
  await fs.writeFile(destPath, js, "utf-8");
@@ -78,10 +95,7 @@ export default async function compile() {
78
95
  );
79
96
  compiled++;
80
97
  } catch (e) {
81
- logger.errorCode(
82
- "NS1",
83
- `${relative.replace(/\\/g, "/")}: ${e.message}`,
84
- );
98
+ logger.errorCode("NS1", `${relative.replace(/\\/g, "/")}: ${e.message}`);
85
99
  errors++;
86
100
  }
87
101
  }
@@ -91,6 +105,10 @@ export default async function compile() {
91
105
  logger.error(`Found ${errors} error(s). Compiled ${compiled} file(s).`);
92
106
  process.exit(1);
93
107
  } else {
94
- logger.success(`Compiled ${compiled} file(s).`);
108
+ let msg = `Compiled ${compiled} file(s).`;
109
+ if (totalWarnings > 0) {
110
+ msg += ` (${totalWarnings} warning(s))`;
111
+ }
112
+ logger.success(msg);
95
113
  }
96
114
  }
@@ -1,102 +1,84 @@
1
1
  import { execSync } from "child_process";
2
2
  import fs from "fs/promises";
3
3
  import path from "path";
4
- import inquirer from "inquirer";
5
- import { handleSigint, isUserCancelled } from "../src/signal-handler.js";
4
+ import { handleSigint } from "../src/signal-handler.js";
6
5
  import * as logger from "../src/logger.js";
7
6
 
8
- export default async function create(projectNameArg) {
7
+ export default async function create(projectNameArg, options = {}) {
9
8
  handleSigint();
10
9
 
11
10
  const cwd = process.cwd();
12
-
13
- try {
14
- // Project name
15
- let projectName = projectNameArg;
16
- if (!projectName) {
17
- const answer = await inquirer.prompt([
18
- {
19
- type: "input",
20
- name: "projectName",
21
- message: "Project name:",
22
- default: "my-noshift-app",
23
- },
24
- ]);
25
- projectName = answer.projectName;
26
- }
27
-
28
- const projectPath = path.join(cwd, projectName);
29
-
30
- // Create project directory
31
- logger.step("Creating project directory...");
32
- await fs.mkdir(projectPath, { recursive: true });
33
- logger.dim(` ${projectPath}`);
34
-
35
- process.chdir(projectPath);
36
-
37
- // npm init
38
- logger.step("Initializing npm...");
39
- execSync("npm init -y", { stdio: "ignore" });
40
-
41
- // Add scripts to package.json
42
- const pkgPath = path.join(projectPath, "package.json");
43
- const pkg = JSON.parse(await fs.readFile(pkgPath, "utf-8"));
44
- pkg.scripts = pkg.scripts ?? {};
45
- pkg.scripts.compile = "nsc compile";
46
- pkg.scripts.dev = "nsc dev";
47
- await fs.writeFile(pkgPath, JSON.stringify(pkg, null, 2) + "\n");
48
-
49
- // Create nsjsconfig.json
50
- const nsjsconfig = {
51
- compilerOptions: {
52
- rootDir: "src",
53
- outDir: "dist",
54
- },
55
- };
56
- await fs.writeFile(
57
- "nsjsconfig.json",
58
- JSON.stringify(nsjsconfig, null, 2) + "\n",
59
- );
60
- logger.success("Created nsjsconfig.json");
61
-
62
- // Prettier
63
- const { usePrettier } = await inquirer.prompt([
64
- {
65
- type: "confirm",
66
- name: "usePrettier",
67
- message: "Format compiled output with Prettier?",
68
- default: true,
69
- },
70
- ]);
71
-
72
- if (usePrettier) {
73
- logger.step("Installing Prettier...");
74
- execSync("npm install --save-dev prettier", { stdio: "ignore" });
75
- await fs.writeFile(".prettierignore", "dist/\nnode_modules/\n");
76
- await fs.writeFile(
77
- ".prettierrc",
78
- JSON.stringify(
79
- { semi: true, singleQuote: false, trailingComma: "es5" },
80
- null,
81
- 2,
82
- ) + "\n",
83
- );
84
- }
85
-
86
- // Install noshift.js
87
- logger.step("Installing noshift.js...");
88
- execSync("npm install noshift.js", { stdio: "ignore" });
89
-
90
- // Create project files
91
- logger.step("Creating project files...");
92
- await fs.mkdir("src", { recursive: true });
11
+ const projectName = projectNameArg || "my-noshift-app";
12
+ const usePrettier = options.prettier !== false; // default: true
13
+
14
+ const projectPath = path.join(cwd, projectName);
15
+
16
+ // Create project directory
17
+ logger.step("Creating project directory...");
18
+ await fs.mkdir(projectPath, { recursive: true });
19
+ logger.dim(` ${projectPath}`);
20
+
21
+ process.chdir(projectPath);
22
+
23
+ // npm init
24
+ logger.step("Initializing npm...");
25
+ execSync("npm init -y", { stdio: "ignore" });
26
+
27
+ // Add scripts to package.json
28
+ const pkgPath = path.join(projectPath, "package.json");
29
+ const pkg = JSON.parse(await fs.readFile(pkgPath, "utf-8"));
30
+ pkg.scripts = pkg.scripts ?? {};
31
+ pkg.scripts.compile = "nsc compile";
32
+ pkg.scripts.dev = "nsc dev";
33
+ await fs.writeFile(pkgPath, JSON.stringify(pkg, null, 2) + "\n");
34
+
35
+ // Create nsjsconfig.json
36
+ const nsjsconfig = {
37
+ compileroptions: {
38
+ rootdir: "src",
39
+ outdir: "dist",
40
+ warnuppercase: true,
41
+ capitalizeinstrings: true,
42
+ },
43
+ };
44
+ await fs.writeFile(
45
+ "nsjsconfig.json",
46
+ JSON.stringify(nsjsconfig, null, 2) + "\n",
47
+ );
48
+ logger.success("Created nsjsconfig.json");
49
+
50
+ // Prettier
51
+ if (usePrettier) {
52
+ logger.step("Installing Prettier...");
53
+ execSync("npm install --save-dev prettier", { stdio: "ignore" });
54
+ await fs.writeFile(".prettierignore", "dist/\nnode_modules/\n");
93
55
  await fs.writeFile(
94
- "src/index.nsjs",
95
- "console.log^8^2Hello, World!^2^9;\n",
56
+ ".prettierrc",
57
+ JSON.stringify(
58
+ { semi: true, singleQuote: false, trailingComma: "es5" },
59
+ null,
60
+ 2,
61
+ ) + "\n",
96
62
  );
63
+ }
64
+
65
+ // Install noshift.js
66
+ logger.step("Installing noshift.js...");
67
+ execSync("npm install noshift.js", { stdio: "ignore" });
97
68
 
98
- // README.md
99
- const readme = `# ${projectName}
69
+ // Create project files
70
+ logger.step("Creating project files...");
71
+ await fs.mkdir("src", { recursive: true });
72
+ await fs.writeFile(
73
+ "src/index.nsjs",
74
+ "console.log^8^2^3hello, ^3world!^2^9;\n",
75
+ );
76
+
77
+ // .gitignore
78
+ await fs.writeFile(".gitignore", "node_modules/\ndist/\n");
79
+
80
+ // README.md
81
+ const readme = `# ${projectName}
100
82
 
101
83
  A [NoShift.js](https://github.com/otoneko1102/NoShift.js) project.
102
84
 
@@ -113,20 +95,14 @@ npm run dev
113
95
  \`\`\`
114
96
  `;
115
97
 
116
- await fs.writeFile("README.md", readme);
117
-
118
- // Success message
119
- console.log("");
120
- logger.success("Project created successfully!");
121
- console.log("");
122
- logger.info("Next steps:");
123
- console.log(` ${logger.highlight(`cd ${projectName}`)}`);
124
- console.log(` ${logger.highlight("npm run compile")}`);
125
- console.log("");
126
- } catch (error) {
127
- if (isUserCancelled(error)) {
128
- process.exit(0);
129
- }
130
- throw error;
131
- }
98
+ await fs.writeFile("README.md", readme);
99
+
100
+ // Success message
101
+ console.log("");
102
+ logger.success("Project created successfully!");
103
+ console.log("");
104
+ logger.info("Next steps:");
105
+ console.log(` ${logger.highlight(`cd ${projectName}`)}`);
106
+ console.log(` ${logger.highlight("npm run compile")}`);
107
+ console.log("");
132
108
  }
package/commands/dev.js CHANGED
@@ -29,13 +29,13 @@ async function findNsjsFiles(dir) {
29
29
  return files;
30
30
  }
31
31
 
32
- async function compileFile(file, rootDir, outDir, cwd) {
32
+ async function compileFile(file, rootDir, outDir, cwd, convertOptions = {}) {
33
33
  const relative = path.relative(rootDir, file).replace(/\\/g, "/");
34
34
  const destPath = path
35
35
  .join(outDir, path.relative(rootDir, file))
36
36
  .replace(/\.nsjs$/, ".js");
37
37
  const code = await fs.readFile(file, "utf-8");
38
- const js = convert(code);
38
+ const js = convert(code, convertOptions);
39
39
  await fs.mkdir(path.dirname(destPath), { recursive: true });
40
40
  await fs.writeFile(destPath, js, "utf-8");
41
41
  logger.dim(
@@ -54,15 +54,18 @@ export default async function dev() {
54
54
  process.exit(1);
55
55
  }
56
56
 
57
- const rootDir = path.resolve(cwd, config.compilerOptions.rootDir);
58
- const outDir = path.resolve(cwd, config.compilerOptions.outDir);
57
+ const rootDir = path.resolve(cwd, config.compileroptions.rootdir);
58
+ const outDir = path.resolve(cwd, config.compileroptions.outdir);
59
+ const convertOptions = {
60
+ capitalizeInStrings: config.compileroptions.capitalizeinstrings !== false,
61
+ };
59
62
 
60
63
  // 初回フルコンパイル
61
64
  const files = await findNsjsFiles(rootDir);
62
65
  if (files === null) {
63
66
  logger.errorCode(
64
67
  "NS0",
65
- `rootDir '${config.compilerOptions.rootDir}' not found.`,
68
+ `rootdir '${config.compileroptions.rootdir}' not found.`,
66
69
  );
67
70
  process.exit(1);
68
71
  }
@@ -73,7 +76,7 @@ export default async function dev() {
73
76
 
74
77
  for (const file of files) {
75
78
  try {
76
- await compileFile(file, rootDir, outDir, cwd);
79
+ await compileFile(file, rootDir, outDir, cwd, convertOptions);
77
80
  } catch (e) {
78
81
  const rel = path.relative(rootDir, file).replace(/\\/g, "/");
79
82
  logger.errorCode("NS1", `${rel}: ${e.message}`);
@@ -81,7 +84,7 @@ export default async function dev() {
81
84
  }
82
85
 
83
86
  logger.info(
84
- `Watching for file changes in '${logger.highlight(config.compilerOptions.rootDir)}'... (Press Ctrl+C to stop)`,
87
+ `Watching for file changes in '${logger.highlight(config.compileroptions.rootdir)}'... (Press Ctrl+C to stop)`,
85
88
  );
86
89
  console.log("");
87
90
 
@@ -109,7 +112,7 @@ export default async function dev() {
109
112
  debounceMap.delete(filename);
110
113
  const absPath = path.join(rootDir, filename);
111
114
  try {
112
- await compileFile(absPath, rootDir, outDir, cwd);
115
+ await compileFile(absPath, rootDir, outDir, cwd, convertOptions);
113
116
  } catch (e) {
114
117
  if (e.code === "ENOENT") {
115
118
  // ファイルが削除された場合はスキップ
package/commands/init.js CHANGED
@@ -4,9 +4,11 @@ import { handleSigint } from "../src/signal-handler.js";
4
4
  import * as logger from "../src/logger.js";
5
5
 
6
6
  const DEFAULT_CONFIG = {
7
- compilerOptions: {
8
- rootDir: "src",
9
- outDir: "dist",
7
+ compileroptions: {
8
+ rootdir: "src",
9
+ outdir: "dist",
10
+ warnuppercase: true,
11
+ capitalizeinstrings: true,
10
12
  },
11
13
  };
12
14
 
@@ -28,7 +30,11 @@ export default async function init() {
28
30
 
29
31
  await writeFile(configPath, JSON.stringify(DEFAULT_CONFIG, null, 2) + "\n");
30
32
  logger.success("Created nsjsconfig.json");
31
- logger.dim(` compilerOptions.rootDir : ${DEFAULT_CONFIG.compilerOptions.rootDir}`);
32
- logger.dim(` compilerOptions.outDir : ${DEFAULT_CONFIG.compilerOptions.outDir}`);
33
+ logger.dim(
34
+ ` compileroptions.rootdir : ${DEFAULT_CONFIG.compileroptions.rootdir}`,
35
+ );
36
+ logger.dim(
37
+ ` compileroptions.outdir : ${DEFAULT_CONFIG.compileroptions.outdir}`,
38
+ );
33
39
  console.log("");
34
40
  }
package/commands/run.js CHANGED
@@ -2,13 +2,26 @@ import { promises as fs } from "fs";
2
2
  import path from "path";
3
3
  import { spawn } from "child_process";
4
4
  import convert from "../src/convert.js";
5
+ import { loadConfig } from "../src/config.js";
5
6
  import { handleSigint } from "../src/signal-handler.js";
6
7
  import * as logger from "../src/logger.js";
7
8
 
8
9
  export default async function run(file) {
9
10
  handleSigint();
10
11
 
11
- const filePath = path.resolve(process.cwd(), file);
12
+ const cwd = process.cwd();
13
+ let config;
14
+ try {
15
+ config = await loadConfig(cwd);
16
+ } catch {
17
+ config = { compileroptions: {} };
18
+ }
19
+
20
+ const convertOptions = {
21
+ capitalizeInStrings: config.compileroptions.capitalizeinstrings !== false,
22
+ };
23
+
24
+ const filePath = path.resolve(cwd, file);
12
25
 
13
26
  let code;
14
27
  try {
@@ -18,7 +31,7 @@ export default async function run(file) {
18
31
  process.exit(1);
19
32
  }
20
33
 
21
- const js = convert(code);
34
+ const js = convert(code, convertOptions);
22
35
 
23
36
  // ソースファイルと同じディレクトリに一時ファイルを作成する。
24
37
  // これにより、コンパイル後コード内の相対 import が正しく解決される。
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "noshift.js",
3
- "version": "0.3.0",
3
+ "version": "0.4.0",
4
4
  "description": "Joke language.",
5
5
  "bin": {
6
6
  "nsc": "./bin/cli.js"
@@ -27,8 +27,7 @@
27
27
  },
28
28
  "homepage": "https://otoneko1102.github.io/NoShift.js/",
29
29
  "dependencies": {
30
- "commander": "^14.0.3",
31
- "inquirer": "^13.2.2"
30
+ "commander": "^14.0.3"
32
31
  },
33
32
  "devDependencies": {
34
33
  "prettier": "^3.8.1"
package/src/config.js CHANGED
@@ -2,9 +2,11 @@ import { promises as fs } from "fs";
2
2
  import path from "path";
3
3
 
4
4
  const DEFAULT_CONFIG = {
5
- compilerOptions: {
6
- rootDir: "src",
7
- outDir: "build",
5
+ compileroptions: {
6
+ rootdir: "src",
7
+ outdir: "dist",
8
+ warnuppercase: true,
9
+ capitalizeinstrings: true,
8
10
  },
9
11
  };
10
12
 
@@ -20,9 +22,9 @@ export async function loadConfig(cwd = process.cwd()) {
20
22
  const userConfig = JSON.parse(raw);
21
23
 
22
24
  return {
23
- compilerOptions: {
24
- ...DEFAULT_CONFIG.compilerOptions,
25
- ...(userConfig.compilerOptions ?? {}),
25
+ compileroptions: {
26
+ ...DEFAULT_CONFIG.compileroptions,
27
+ ...(userConfig.compileroptions ?? {}),
26
28
  },
27
29
  };
28
30
  } catch (e) {
package/src/convert.js CHANGED
@@ -1,4 +1,5 @@
1
- function convertNsjsToJs(nsjsCode) {
1
+ function convertNsjsToJs(nsjsCode, options = {}) {
2
+ const capitalizeInStrings = options.capitalizeInStrings !== false;
2
3
  let jsCode = "";
3
4
  let i = 0;
4
5
  const len_ns = nsjsCode.length;
@@ -15,6 +16,8 @@ function convertNsjsToJs(nsjsCode) {
15
16
  IN_TEMPLATE_EXPRESSION: "IN_TEMPLATE_EXPRESSION", // ${ … } の中
16
17
  RAW_DQ_IN_EXPR: "RAW_DQ_IN_EXPR", // テンプレート式内の " … " の中 (NoShift 変換なし)
17
18
  RAW_SQ_IN_EXPR: "RAW_SQ_IN_EXPR", // テンプレート式内の ' … ' の中 (NoShift 変換なし)
19
+ IN_LINE_COMMENT: "IN_LINE_COMMENT", // // … の中
20
+ IN_BLOCK_COMMENT: "IN_BLOCK_COMMENT", // /^: … ^:/ の中
18
21
  };
19
22
 
20
23
  let currentState = STATE.NORMAL;
@@ -236,7 +239,11 @@ function convertNsjsToJs(nsjsCode) {
236
239
 
237
240
  // (A) IN_DQ_STRING 内 (" … ")
238
241
  if (currentState === STATE.IN_DQ_STRING) {
239
- if (nsjsCode.startsWith("\\^2", i)) {
242
+ if (nsjsCode.startsWith("\\^3", i)) {
243
+ jsCode += "^3"; // "\^3" を文字列中のリテラル "^3" として出力
244
+ i += 3;
245
+ consumed = true;
246
+ } else if (nsjsCode.startsWith("\\^2", i)) {
240
247
  jsCode += "^2"; // "\^2" を文字列中の "^2" として出力
241
248
  i += 3;
242
249
  consumed = true;
@@ -248,7 +255,11 @@ function convertNsjsToJs(nsjsCode) {
248
255
  }
249
256
  // (B) IN_SQ_STRING 内 (' … ')
250
257
  else if (currentState === STATE.IN_SQ_STRING) {
251
- if (nsjsCode.startsWith("\\^7", i)) {
258
+ if (nsjsCode.startsWith("\\^3", i)) {
259
+ jsCode += "^3";
260
+ i += 3;
261
+ consumed = true;
262
+ } else if (nsjsCode.startsWith("\\^7", i)) {
252
263
  jsCode += "^7";
253
264
  i += 3;
254
265
  consumed = true;
@@ -260,7 +271,11 @@ function convertNsjsToJs(nsjsCode) {
260
271
  }
261
272
  // (C) RAW_DQ_IN_EXPR 内 (テンプレート式中の " … ")
262
273
  else if (currentState === STATE.RAW_DQ_IN_EXPR) {
263
- if (nsjsCode.startsWith("\\^2", i)) {
274
+ if (nsjsCode.startsWith("\\^3", i)) {
275
+ jsCode += "^3";
276
+ i += 3;
277
+ consumed = true;
278
+ } else if (nsjsCode.startsWith("\\^2", i)) {
264
279
  jsCode += "^2";
265
280
  i += 3;
266
281
  consumed = true;
@@ -284,7 +299,11 @@ function convertNsjsToJs(nsjsCode) {
284
299
  }
285
300
  // (D) RAW_SQ_IN_EXPR 内 (テンプレート式中の ' … ')
286
301
  else if (currentState === STATE.RAW_SQ_IN_EXPR) {
287
- if (nsjsCode.startsWith("\\^7", i)) {
302
+ if (nsjsCode.startsWith("\\^3", i)) {
303
+ jsCode += "^3";
304
+ i += 3;
305
+ consumed = true;
306
+ } else if (nsjsCode.startsWith("\\^7", i)) {
288
307
  jsCode += "^7";
289
308
  i += 3;
290
309
  consumed = true;
@@ -308,7 +327,11 @@ function convertNsjsToJs(nsjsCode) {
308
327
  }
309
328
  // (E) IN_BT_SINGLE_STRING 内 (` … `)
310
329
  else if (currentState === STATE.IN_BT_SINGLE_STRING) {
311
- if (nsjsCode.startsWith("\\^@", i)) {
330
+ if (nsjsCode.startsWith("\\^3", i)) {
331
+ jsCode += "^3";
332
+ i += 3;
333
+ consumed = true;
334
+ } else if (nsjsCode.startsWith("\\^@", i)) {
312
335
  jsCode += "^@";
313
336
  i += 3;
314
337
  consumed = true;
@@ -333,14 +356,93 @@ function convertNsjsToJs(nsjsCode) {
333
356
  }
334
357
 
335
358
  // ======
336
- // ステップ2: NoShift シーケンスや文字列/テンプレートの開閉、式展開を試す
359
+ // ステップ2: ^3 大文字化モディファイア (RAW 状態とコメント以外で動作)
360
+ // - コード上 (NORMAL, IN_TEMPLATE_EXPRESSION) では常に有効
361
+ // - 文字列内は capitalizeInStrings オプションに従う
362
+ // ======
363
+ if (
364
+ !consumed &&
365
+ currentState !== STATE.RAW_DQ_IN_EXPR &&
366
+ currentState !== STATE.RAW_SQ_IN_EXPR &&
367
+ currentState !== STATE.IN_LINE_COMMENT &&
368
+ currentState !== STATE.IN_BLOCK_COMMENT
369
+ ) {
370
+ if (nsjsCode.startsWith("^3", i)) {
371
+ const inString =
372
+ currentState === STATE.IN_DQ_STRING ||
373
+ currentState === STATE.IN_SQ_STRING ||
374
+ currentState === STATE.IN_BT_SINGLE_STRING ||
375
+ currentState === STATE.IN_BT_MULTI_STRING;
376
+ if (!inString || capitalizeInStrings) {
377
+ i += 2;
378
+ if (i < len_ns) {
379
+ jsCode += nsjsCode[i].toUpperCase();
380
+ i += 1;
381
+ }
382
+ consumed = true;
383
+ }
384
+ }
385
+ }
386
+
387
+ // ======
388
+ // ステップ2.5: コメントの処理
389
+ // ======
390
+ if (!consumed) {
391
+ // 行コメント開始 (//)
392
+ if (currentState === STATE.NORMAL && nsjsCode.startsWith("//", i)) {
393
+ jsCode += "//";
394
+ i += 2;
395
+ stateStack.push(currentState);
396
+ currentState = STATE.IN_LINE_COMMENT;
397
+ consumed = true;
398
+ }
399
+ // 行コメント終了 (改行)
400
+ else if (currentState === STATE.IN_LINE_COMMENT) {
401
+ if (nsjsCode[i] === "\n") {
402
+ jsCode += "\n";
403
+ i += 1;
404
+ currentState = stateStack.pop();
405
+ } else {
406
+ jsCode += nsjsCode[i];
407
+ i += 1;
408
+ }
409
+ consumed = true;
410
+ }
411
+ // ブロックコメント開始 (/^:)
412
+ else if (currentState === STATE.NORMAL && nsjsCode.startsWith("/^:", i)) {
413
+ jsCode += "/*";
414
+ i += 3;
415
+ stateStack.push(currentState);
416
+ currentState = STATE.IN_BLOCK_COMMENT;
417
+ consumed = true;
418
+ }
419
+ // ブロックコメント終了 (^:/)
420
+ else if (
421
+ currentState === STATE.IN_BLOCK_COMMENT &&
422
+ nsjsCode.startsWith("^:/", i)
423
+ ) {
424
+ jsCode += "*/";
425
+ i += 3;
426
+ currentState = stateStack.pop();
427
+ consumed = true;
428
+ }
429
+ // ブロックコメント内の文字 (そのまま出力)
430
+ else if (currentState === STATE.IN_BLOCK_COMMENT) {
431
+ jsCode += nsjsCode[i];
432
+ i += 1;
433
+ consumed = true;
434
+ }
435
+ }
436
+
437
+ // ======
438
+ // ステップ3: NoShift シーケンスや文字列/テンプレートの開閉、式展開を試す
337
439
  // ======
338
440
  if (!consumed) {
339
441
  consumed = tryConsumeNsjsSequence();
340
442
  }
341
443
 
342
444
  // ======
343
- // ステップ3: 何も消費しなかったら文字をそのまま出力
445
+ // ステップ4: 何も消費しなかったら文字をそのまま出力
344
446
  // ======
345
447
  if (!consumed) {
346
448
  jsCode += nsjsCode[i];
@@ -359,4 +461,139 @@ function convertNsjsToJs(nsjsCode) {
359
461
  return jsCode;
360
462
  }
361
463
 
464
+ /**
465
+ * ソースコード内の大文字、_, $, # の使用を警告する。
466
+ * 文字列・コメント内は無視する。
467
+ * @param {string} nsjsCode
468
+ * @returns {{ line: number, column: number, char: string, message: string }[]}
469
+ */
470
+ export function checkUppercaseWarnings(nsjsCode) {
471
+ const warnings = [];
472
+ const lines = nsjsCode.split("\n");
473
+
474
+ // シンプルな状態追跡(文字列・コメント内を除外)
475
+ let inDQ = false; // ^2...^2
476
+ let inSQ = false; // ^7...^7
477
+ let inBT = false; // ^@...^@
478
+ let inLineComment = false;
479
+ let inBlockComment = false;
480
+
481
+ for (let lineNum = 0; lineNum < lines.length; lineNum++) {
482
+ const line = lines[lineNum];
483
+ inLineComment = false; // 行コメントは行ごとにリセット
484
+
485
+ for (let col = 0; col < line.length; col++) {
486
+ const ch = line[col];
487
+ const next = line[col + 1];
488
+
489
+ // エスケープ (\^2, \^7, \^@) をスキップ
490
+ if (ch === "\\" && next === "^") {
491
+ col += 2;
492
+ continue;
493
+ }
494
+
495
+ // ブロックコメント終了 (^:/)
496
+ if (
497
+ inBlockComment &&
498
+ ch === "^" &&
499
+ next === ":" &&
500
+ line[col + 2] === "/"
501
+ ) {
502
+ inBlockComment = false;
503
+ col += 2;
504
+ continue;
505
+ }
506
+ if (inBlockComment) continue;
507
+
508
+ // 行コメント開始
509
+ if (!inDQ && !inSQ && !inBT && ch === "/" && next === "/") {
510
+ inLineComment = true;
511
+ break;
512
+ }
513
+ // ブロックコメント開始 (/^:)
514
+ if (
515
+ !inDQ &&
516
+ !inSQ &&
517
+ !inBT &&
518
+ ch === "/" &&
519
+ next === "^" &&
520
+ line[col + 2] === ":"
521
+ ) {
522
+ inBlockComment = true;
523
+ col += 2;
524
+ continue;
525
+ }
526
+
527
+ if (inLineComment) continue;
528
+
529
+ // ^3 モディファイア → 次の文字はスキップ(意図的な大文字化)
530
+ if (ch === "^" && next === "3") {
531
+ col += 2; // ^3 と次の文字をスキップ
532
+ continue;
533
+ }
534
+
535
+ // 文字列リテラルの開閉
536
+ if (ch === "^" && next === "2") {
537
+ inDQ = !inDQ;
538
+ col += 1;
539
+ continue;
540
+ }
541
+ if (ch === "^" && next === "7") {
542
+ inSQ = !inSQ;
543
+ col += 1;
544
+ continue;
545
+ }
546
+ if (ch === "^" && next === "@") {
547
+ inBT = !inBT;
548
+ col += 1;
549
+ continue;
550
+ }
551
+
552
+ // 文字列内はスキップ
553
+ if (inDQ || inSQ || inBT) continue;
554
+
555
+ // 他の ^X シーケンスをスキップ
556
+ if (ch === "^" && next && /[0-9\-^\\@\[\];:,./]/.test(next)) {
557
+ col += 1;
558
+ continue;
559
+ }
560
+
561
+ // 大文字の警告
562
+ if (/[A-Z]/.test(ch)) {
563
+ warnings.push({
564
+ line: lineNum + 1,
565
+ column: col + 1,
566
+ char: ch,
567
+ message: `Uppercase letter '${ch}' found. Use ^3${ch.toLowerCase()} instead.`,
568
+ });
569
+ }
570
+ // _, $, # の警告
571
+ else if (ch === "_") {
572
+ warnings.push({
573
+ line: lineNum + 1,
574
+ column: col + 1,
575
+ char: ch,
576
+ message: "Underscore '_' found in code.",
577
+ });
578
+ } else if (ch === "$") {
579
+ warnings.push({
580
+ line: lineNum + 1,
581
+ column: col + 1,
582
+ char: ch,
583
+ message: "Dollar sign '$' found. Use ^4 instead.",
584
+ });
585
+ } else if (ch === "#") {
586
+ warnings.push({
587
+ line: lineNum + 1,
588
+ column: col + 1,
589
+ char: ch,
590
+ message: "Hash '#' found in code.",
591
+ });
592
+ }
593
+ }
594
+ }
595
+
596
+ return warnings;
597
+ }
598
+
362
599
  export default convertNsjsToJs;
package/src/logger.js CHANGED
@@ -46,7 +46,5 @@ export function highlight(text) {
46
46
  }
47
47
 
48
48
  export function errorCode(code, message) {
49
- console.error(
50
- `${colors.red}error ${code}:${colors.reset} ${message}`,
51
- );
49
+ console.error(`${colors.red}error ${code}:${colors.reset} ${message}`);
52
50
  }