Repository: aiscript-dev/aiscript
Pull request #894 (2025-01-02 13:33:55)

enhance: オブジェクトリテラルのキーに文字列を書けるように
<!-- ℹ お読みください
PRありがとうございます！ PRを作成する前に、以下をご確認ください:
- 可能であればタイトルに、以下で示すようなPRの種類が分かるキーワードをプリフィクスしてください。
  - fix / refactor / feat / enhance / perf / chore
  - また、PRの粒度が適切であることを確認してください。ひとつのPRに複数の種類の変更や関心を含めることは避けてください。
- このPRによって解決されるIssueがある場合は、そのIssueへの参照を本文内に含めてください。
- unreleasedディレクトリ以下にファイルを新規に作成し、変更点を記してください。リファクタリングなど、利用者に影響を与えない変更についてはこの限りではありません。
- この変更により新たに作成、もしくは更新すべきドキュメントがないか確認してください。
- 機能追加やバグ修正をした場合は、可能であればテストケースを追加してください。
- テスト、Lintが通っていることを予め確認してください。
  - `npm run test`、`npm run lint`でぞれぞれ実施可能です
- `npm run api`を実行してAPIレポートを更新し、差分がある場合はコミットしてください。
ご協力ありがとうございます🤗
-->
<!-- ℹ README
Thank you for your PR! Before creating a PR, please check the following:
- If possible, prefix the title with a keyword that identifies the type of this PR, as shown below.
  - fix / refactor / feat / enhance / perf / chore
  - Also, make sure that the granularity of this PR is appropriate. Please do not include more than one type of change or interest in a single PR.
- If there is an Issue which will be resolved by this PR, please include a reference to the Issue in the text.
- Please create a new file under the `unreleased` directory and write down the summary of your changes. However, this is not necessary for changes that do not affect the users, such as refactoring.
- Check if there are any documents that need to be created or updated due to this change.
- If you have added a feature or fixed a bug, please add a test case if possible.
- Please make sure that tests and Lint are passed in advance.
  - You can run it with `npm run test` and `npm run lint`.
- Run `npm run api` to update the API report and commit it if there are any diffs.
Thanks for your cooperation 🤗
-->

# What
<!-- このPRで何をしたのか？ どう変わるのか？ -->
<!-- What did you do with this PR? How will it change things? -->
オブジェクトリテラルのキーに文字列リテラルを使用できるようにします。

# Why
<!-- なぜそうするのか？ どういう意図なのか？ 何が困っているのか？ -->
<!-- Why do you do it? What are your intentions? What is the problem? -->
- #62

# Additional info (optional)
<!-- テスト観点など -->
<!-- Test perspective, etc -->


codecov-commenter: :warning: Please install the !['codecov app svg image'](https://github.com/codecov/engineering-team/assets/152432831/e90313f4-9d3a-4b63-8b54-cfe14e7ec20d) to ensure uploads and comments are reliably processed by Codecov.

## [Codecov](https://app.codecov.io/gh/aiscript-dev/aiscript/pull/894?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None) Report
All modified and coverable lines are covered by tests :white_check_mark:

:exclamation: Your organization needs to install the [Codecov GitHub app](https://github.com/apps/codecov/installations/select_target) to enable full functionality.

| [Files with missing lines](https://app.codecov.io/gh/aiscript-dev/aiscript/pull/894?dropdown=coverage&src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None) | Coverage Δ | |
|---|---|---|
| [src/parser/syntaxes/expressions.ts](https://app.codecov.io/gh/aiscript-dev/aiscript/pull/894?src=pr&el=tree&filepath=src%2Fparser%2Fsyntaxes%2Fexpressions.ts&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None#diff-c3JjL3BhcnNlci9zeW50YXhlcy9leHByZXNzaW9ucy50cw==) | `94.79% <100.00%> (+5.61%)` | :arrow_up: |

... and [29 files with indirect coverage changes](https://app.codecov.io/gh/aiscript-dev/aiscript/pull/894/indirect-changes?src=pr&el=tree-more&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None)


Patch:
diff --git a/package-lock.json b/package-lock.json
index b3f0339..ebe5441 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -19,7 +19,6 @@
 				"@types/eslint__js": "8.42.3",
 				"@types/node": "22.10.2",
 				"@types/seedrandom": "3.0.8",
-				"@types/uuid": "10.0.0",
 				"@vitest/coverage-v8": "2.1.8",
 				"chalk": "5.4.1",
 				"eslint": "9.17.0",
@@ -1394,13 +1393,6 @@
 			"dev": true,
 			"license": "MIT"
 		},
-		"node_modules/@types/uuid": {
-			"version": "10.0.0",
-			"resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-10.0.0.tgz",
-			"integrity": "sha512-7gqG38EyHgyP1S+7+xomFtL+ZNHcKv6DwNaCZmJmo1vgMugyF3TCnXVg4t1uk89mLNwnLtnY3TpOpCOyp1/xHQ==",
-			"dev": true,
-			"license": "MIT"
-		},
 		"node_modules/@typescript-eslint/eslint-plugin": {
 			"version": "8.18.2",
 			"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.18.2.tgz",
diff --git a/package.json b/package.json
index b48f1d1..0edba85 100644
--- a/package.json
+++ b/package.json
@@ -39,7 +39,6 @@
 		"@types/eslint__js": "8.42.3",
 		"@types/node": "22.10.2",
 		"@types/seedrandom": "3.0.8",
-		"@types/uuid": "10.0.0",
 		"@vitest/coverage-v8": "2.1.8",
 		"chalk": "5.4.1",
 		"eslint": "9.17.0",
diff --git a/playground/package-lock.json b/playground/package-lock.json
index 0d7c797..9487ea1 100644
--- a/playground/package-lock.json
+++ b/playground/package-lock.json
@@ -29,21 +29,20 @@
         "uuid": "11.0.3"
       },
       "devDependencies": {
-        "@eslint/js": "9.15.0",
-        "@microsoft/api-extractor": "7.48.0",
+        "@eslint/js": "9.17.0",
+        "@microsoft/api-extractor": "7.48.1",
         "@types/eslint__js": "8.42.3",
-        "@types/node": "22.10.1",
+        "@types/node": "22.10.2",
         "@types/seedrandom": "3.0.8",
-        "@types/uuid": "10.0.0",
-        "@vitest/coverage-v8": "2.1.6",
-        "chalk": "5.3.0",
-        "eslint": "9.15.0",
+        "@vitest/coverage-v8": "2.1.8",
+        "chalk": "5.4.1",
+        "eslint": "9.17.0",
         "eslint-plugin-import": "2.31.0",
         "semver": "7.6.3",
         "ts-node": "10.9.2",
         "typescript": "5.7.2",
-        "typescript-eslint": "8.16.0",
-        "vitest": "2.1.6"
+        "typescript-eslint": "8.18.2",
+        "vitest": "2.1.8"
       }
     },
     "node_modules/@babel/helper-string-parser": {
diff --git a/src/parser/scanner.ts b/src/parser/scanner.ts
index 5cab470..2802b06 100644
--- a/src/parser/scanner.ts
+++ b/src/parser/scanner.ts
@@ -628,10 +628,13 @@ export class Scanner implements ITokenStream {
 	private skipCommentRange(): void {
 		while (true) {
 			if (this.stream.eof) {
-				break;
+				throw new AiScriptUnexpectedEOFError(this.stream.getPos());
 			}
 			if (this.stream.char === '*') {
 				this.stream.next();
+				if (this.stream.eof) {
+					throw new AiScriptUnexpectedEOFError(this.stream.getPos());
+				}
 				if ((this.stream.char as string) === '/') {
 					this.stream.next();
 					break;
diff --git a/src/parser/syntaxes/expressions.ts b/src/parser/syntaxes/expressions.ts
index 5d6e264..ca86cd9 100644
--- a/src/parser/syntaxes/expressions.ts
+++ b/src/parser/syntaxes/expressions.ts
@@ -565,7 +565,10 @@ function parseObject(s: ITokenStream, isStatic: boolean): Ast.Obj {
 
 	const map = new Map<string, Ast.Expression>();
 	while (!s.is(TokenKind.CloseBrace)) {
-		s.expect(TokenKind.Identifier);
+		const keyTokenKind = s.getTokenKind();
+		if (keyTokenKind !== TokenKind.Identifier && keyTokenKind !== TokenKind.StringLiteral) {
+			throw unexpectedTokenError(keyTokenKind, s.getPos());
+		}
 		const k = s.getTokenValue();
 		s.next();
 
diff --git a/unreleased/object-key-string.md b/unreleased/object-key-string.md
new file mode 100644
index 0000000..b04b8f2
--- /dev/null
+++ b/unreleased/object-key-string.md
@@ -0,0 +1,1 @@
+- オブジェクトリテラルのキーに文字列リテラルを記述できるようになりました。


Test patch:
diff --git a/test/index.ts b/test/index.ts
index af97bf1..e66b855 100644
--- a/test/index.ts
+++ b/test/index.ts
@@ -198,6 +198,8 @@ describe('Object', () => {
 	});
 
 	/* 未実装
+	 * see also: test/literals.ts > literal > obj (string key)
+	 * issue: https://github.com/aiscript-dev/aiscript/issues/62
 	test.concurrent('string key', async () => {
 		const res = await exe(`
 		let obj = {
diff --git a/test/literals.ts b/test/literals.ts
index 772ba99..462cb5b 100644
--- a/test/literals.ts
+++ b/test/literals.ts
@@ -130,6 +130,15 @@ describe('literal', () => {
 		eq(res, OBJ(new Map([['a', NUM(1)], ['b', NUM(2)], ['c', NUM(3)]])));
 	});
 
+	test.concurrent('obj (string key)', async () => {
+		const res = await exe(`
+		<: {
+			"藍": 42,
+		}
+		`);
+		eq(res, OBJ(new Map([['藍', NUM(42)]])));
+	});
+
 	test.concurrent('obj and arr (separated by line break)', async () => {
 		const res = await exe(`
 		<: {
diff --git a/test/syntax.ts b/test/syntax.ts
index e6b3b93..f837929 100644
--- a/test/syntax.ts
+++ b/test/syntax.ts
@@ -2,7 +2,7 @@ import * as assert from 'assert';
 import { describe, test } from 'vitest';
 import { utils } from '../src';
 import { NUM, STR, NULL, ARR, OBJ, BOOL, TRUE, FALSE, ERROR ,FN_NATIVE } from '../src/interpreter/value';
-import { AiScriptRuntimeError } from '../src/error';
+import { AiScriptRuntimeError, AiScriptUnexpectedEOFError } from '../src/error';
 import { exe, getMeta, eq } from './testutils';
 
 /*
@@ -514,6 +514,16 @@ describe('Comment', () => {
 		`);
 		eq(res, STR('a'));
 	});
+
+	test.concurrent('invalid EOF in multi line comment', async () => {
+		await assert.rejects(() => exe(`
+		/* comment
+		`), AiScriptUnexpectedEOFError);
+	});
+
+	test.concurrent('invalid EOF in multi line comment 2', async () => {
+		await assert.rejects(() => exe('/* comment *'), AiScriptUnexpectedEOFError);
+	});
 });
 
 describe('lang version', () => {