@uiwwsw/hangulscript 0.6.0 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +28 -0
- package/README.md +17 -7
- package/dist/checker.js +80 -0
- package/dist/cli.js +5 -9
- package/dist/diagnostics.js +61 -0
- package/dist/editor.d.ts +5 -0
- package/dist/editor.js +50 -0
- package/dist/emitter.js +39 -0
- package/dist/formatter.d.ts +2 -0
- package/dist/formatter.js +29 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.js +33 -139
- package/dist/loader.js +16 -0
- package/dist/register.js +2 -0
- package/dist/runtime.js +0 -1
- package/dist/sentences.js +13 -7
- package/dist/transformer.d.ts +3 -0
- package/dist/transformer.js +6 -0
- package/dist/vite.js +18 -11
- package/docs/frameworks.md +152 -0
- package/docs/support.md +42 -0
- package/package.json +21 -5
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# 0.7.0
|
|
2
|
+
|
|
3
|
+
- 공식 VS Code 확장 0.2.0: 문법 강조·포맷·진단·타입 정보·정의 이동·스코프 자동 완성·스니펫.
|
|
4
|
+
- 실제 VS Code Extension Host 통합 테스트와 CI 추가.
|
|
5
|
+
- Node 등록 진입점과 CLI에서 상대 경로 .hgs 모듈 직접 실행.
|
|
6
|
+
- 다중 파일 JS·타입 선언 빌드와 .js import 경로 연결.
|
|
7
|
+
- tsconfig compilerOptions·extends 적용과 지원·호환성 계약 문서화.
|
|
8
|
+
|
|
9
|
+
# 0.7.0-dev.0 (개발 버전)
|
|
10
|
+
|
|
11
|
+
- VS Code 확장 0.1.0: `.hgs` 문법 강조, 들여쓰기, 문서 포맷, Problems 진단.
|
|
12
|
+
- Vite·CLI·브라우저의 문법·타입 진단에 원본 HGS 위치 표시.
|
|
13
|
+
- 포맷터 API와 확장용 VSIX 빌드 추가.
|
|
14
|
+
|
|
15
|
+
# 변경 내역
|
|
16
|
+
|
|
17
|
+
## 0.6.0
|
|
18
|
+
|
|
19
|
+
문장형이 유일한 기본 문법입니다. 소스는 첫 문장부터 바로 작성합니다.
|
|
20
|
+
|
|
21
|
+
- **호환되지 않는 변경:** 키워드형 소스 모드·시작 표기·`.ks` 입력을 제거했습니다. 기존 파일은 `.hgs`와 문장형으로 옮겨야 합니다.
|
|
22
|
+
- 모드 감지·전역 키워드 치환·클래스 사후 보정을 제거했습니다.
|
|
23
|
+
- 모듈 가져오기·공개, 비동기·기다리기, 생성기, 예외 처리, 클래스·상속·읽기/쓰기 속성, 자료형·열거값·제네릭을 문장형으로 제공합니다.
|
|
24
|
+
- 잘못된 조건 분기 연결과 반복/함수 밖의 제어 문장을 진단합니다. 문자열·정규식·템플릿·속성 이름을 보존합니다.
|
|
25
|
+
- 기본 함수 15개, 화면 함수 5개, CLI JS+.d.ts, React/Vue Vite 연동을 검증했습니다.
|
|
26
|
+
- 기본 예제의 입력창은 즉시 표시하며 코드는 버튼을 눌렀을 때 실행합니다.
|
|
27
|
+
|
|
28
|
+
지원 문법과 제약은 [LANGUAGE.md](LANGUAGE.md), React·Vue 연동은 [가이드](docs/frameworks.md)를 참고하세요.
|
package/README.md
CHANGED
|
@@ -3,12 +3,12 @@
|
|
|
3
3
|
[](https://github.com/uiwwsw/hangulscript/actions/workflows/ci.yml)
|
|
4
4
|
[](https://github.com/uiwwsw/hangulscript/blob/main/LICENSE)
|
|
5
5
|
|
|
6
|
-
**한글 문장으로 작성하고, JavaScript와 타입 선언으로 배포하는
|
|
6
|
+
**한글 문장으로 작성하고, JavaScript와 타입 선언으로 배포하는 프로그래밍 언어.**
|
|
7
7
|
|
|
8
8
|
Python의 들여쓰기와 익숙한 기본 함수, JavaScript의 표현식과 웹 생태계,
|
|
9
9
|
TypeScript의 타입 검사를 결합합니다. 소스 확장자는 **`.hgs`**입니다.
|
|
10
10
|
|
|
11
|
-
[온라인에서 실행하기](https://hangulscript.vercel.app/) · [React·Vue 사용법](docs/frameworks.md) · [문법 명세](LANGUAGE.md) · [릴리스](https://github.com/uiwwsw/hangulscript/releases) · [의견 보내기](https://github.com/uiwwsw/hangulscript/issues)
|
|
11
|
+
[온라인에서 실행하기](https://hangulscript.vercel.app/) · [React·Vue 사용법](docs/frameworks.md) · [문법 명세](LANGUAGE.md) · [변경 내역](CHANGELOG.md) · [릴리스](https://github.com/uiwwsw/hangulscript/releases) · [의견 보내기](https://github.com/uiwwsw/hangulscript/issues)
|
|
12
12
|
|
|
13
13
|
```text
|
|
14
14
|
점수들은 [75, 92, 88]이다
|
|
@@ -18,7 +18,7 @@ TypeScript의 타입 검사를 결합합니다. 소스 확장자는 **`.hgs`**
|
|
|
18
18
|
출력("평균:", 평균(점수들))
|
|
19
19
|
```
|
|
20
20
|
|
|
21
|
-
|
|
21
|
+
정해진 한국어 문법을 파싱하며, 임의의 자연어를 추측해 실행하지 않습니다.
|
|
22
22
|
Python 소스 호환이나 전체 TypeScript 문법 지원을 뜻하지 않습니다.
|
|
23
23
|
|
|
24
24
|
## 지금 만들 수 있는 것
|
|
@@ -39,7 +39,7 @@ Python 소스 호환이나 전체 TypeScript 문법 지원을 뜻하지 않습
|
|
|
39
39
|
|
|
40
40
|
## 설치
|
|
41
41
|
|
|
42
|
-
Node.js **20 이상**이 필요합니다. 아래의 JS + .d.ts 빌드와 `run` 명령은 **0.3.0부터** 제공합니다.
|
|
42
|
+
Node.js **20.19 이상**이 필요합니다. 아래의 JS + .d.ts 빌드와 `run` 명령은 **0.3.0부터** 제공합니다.
|
|
43
43
|
공개된 버전은 [npm](https://www.npmjs.com/package/@uiwwsw/hangulscript) 또는 `npm view @uiwwsw/hangulscript version`으로 확인하세요.
|
|
44
44
|
|
|
45
45
|
```bash
|
|
@@ -235,7 +235,7 @@ Vite 연동을 제공합니다. 독립 AST·원본 `.hgs` 소스맵·LSP·모듈
|
|
|
235
235
|
|
|
236
236
|
[MIT](LICENSE)
|
|
237
237
|
|
|
238
|
-
## 이름과 확장자
|
|
238
|
+
## 이름과 확장자
|
|
239
239
|
|
|
240
240
|
제품명은 한글스크립트(HangulScript), 패키지는 `@uiwwsw/hangulscript`입니다.
|
|
241
241
|
0.6부터 소스 확장자는 `.hgs`만 지원하며 키워드형과 시작 표기를 제거했습니다. 기존 코드는 [문법 명세](LANGUAGE.md)의 문장형으로 옮겨야 합니다.
|
|
@@ -270,7 +270,7 @@ HTML·이벤트 핸들러 문자열을 삽입하지 않습니다.
|
|
|
270
270
|
**0.6.0 이상**의 공통 Vite 플러그인을 사용합니다. React/Vue의 화면과 상태 관리에 한글스크립트 계산·검증 함수를 연결할 수 있습니다.
|
|
271
271
|
|
|
272
272
|
```bash
|
|
273
|
-
npm install --save-dev @uiwwsw/hangulscript@^0.
|
|
273
|
+
npm install --save-dev @uiwwsw/hangulscript@^0.7.0
|
|
274
274
|
```
|
|
275
275
|
|
|
276
276
|
```js
|
|
@@ -303,5 +303,15 @@ console.log(결제금액(10000, 20)); // 8000
|
|
|
303
303
|
- [온라인 연동 안내](https://hangulscript.vercel.app/#frameworks)
|
|
304
304
|
|
|
305
305
|
예제는 Node.js `^20.19.0 || >=22.12.0`이 필요하며 Node.js 24에서 검증했습니다.
|
|
306
|
-
Vite 플러그인은
|
|
306
|
+
**0.7.0부터** Vite 플러그인은 구문·타입 오류가 있으면 개발 서버와 빌드를 실패시키고 원본 `.hgs` 위치를 표시합니다. 선언 생성·디버깅 소스맵은 제공하지 않습니다. TypeScript 프로젝트는 `ksc build`로 생성한 `.js`와 `.d.ts`를 가져오는 방식이 적합합니다.
|
|
307
307
|
데모의 UI 문장을 React/Vue 컴포넌트로 자동 변환하는 기능은 포함하지 않습니다.
|
|
308
|
+
|
|
309
|
+
## VS Code 확장
|
|
310
|
+
|
|
311
|
+
문법 강조·자동 들여쓰기·문서 포맷·Problems 오류 표시·타입 정보·정의 이동·스코프 자동 완성을 제공하는 확장을 같은 저장소의 [`extensions/vscode`](extensions/vscode)에서 개발합니다. [설치·저장 시 포맷·Marketplace 게시 안내](extensions/vscode/README.md)를 참고하세요. VSIX 패키지와 Marketplace 공개는 별도 단계입니다.
|
|
312
|
+
|
|
313
|
+
## 공식 도구와 지원 범위
|
|
314
|
+
|
|
315
|
+
Node에서 직접 실행하려면 `node --import @uiwwsw/hangulscript/register src/main.hgs`를 사용합니다. `ksc run`도 같은 로더를 사용하며 상대 경로 `.hgs` import를 처리합니다. `ksc build`는 진입 폴더 아래 HGS 모듈을 함께 빌드하고 import를 `.js`로 연결합니다.
|
|
316
|
+
|
|
317
|
+
[환경별 지원 계약·타입 설정·호환성 정책](docs/support.md)을 확인하세요. 언어 패키지와 VS Code 확장은 같은 저장소에서 검증하며 별도 버전으로 배포합니다.
|
package/dist/checker.js
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import ts from 'typescript';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { compileMappedSource } from './transformer.js';
|
|
4
|
+
import { mapDiagnostic, syntaxDiagnostic } from './diagnostics.js';
|
|
5
|
+
|
|
6
|
+
const libraryCache = new Map();
|
|
7
|
+
export function analyzeSource(source, filePath = 'input.hgs', settings = {}) {
|
|
8
|
+
const configPath = ts.findConfigFile(path.dirname(path.resolve(filePath)), ts.sys.fileExists);
|
|
9
|
+
const configRead = configPath ? ts.readConfigFile(configPath, ts.sys.readFile) : undefined;
|
|
10
|
+
const converted = configRead?.config ? ts.parseJsonConfigFileContent(configRead.config, { ...ts.sys, readDirectory: () => [] }, path.dirname(configPath)) : { options: {}, errors: [] };
|
|
11
|
+
const options = {
|
|
12
|
+
strict: true, skipLibCheck: true, types: [],
|
|
13
|
+
...converted.options, ...settings.compilerOptions,
|
|
14
|
+
target: ts.ScriptTarget.ES2022, module: ts.ModuleKind.ESNext, moduleResolution: ts.ModuleResolutionKind.Bundler,
|
|
15
|
+
moduleDetection: ts.ModuleDetectionKind.Force, declaration: true, noEmitOnError: true,
|
|
16
|
+
noEmit: false, emitDeclarationOnly: false, outDir: undefined, outFile: undefined, rootDir: undefined, incremental: false, composite: false, sourceMap: false, declarationMap: false, inlineSourceMap: false
|
|
17
|
+
};
|
|
18
|
+
const originals = new Map([...settings.files ?? []].map(([name, text]) => [path.resolve(name), text]));
|
|
19
|
+
const root = path.resolve(filePath);
|
|
20
|
+
originals.set(root, source);
|
|
21
|
+
const virtual = name => name + '.ts';
|
|
22
|
+
const documents = new Map();
|
|
23
|
+
const syntaxErrors = [];
|
|
24
|
+
const base = ts.createCompilerHost(options, true);
|
|
25
|
+
const libDirectory = settings.libDirectory ?? path.dirname(ts.getDefaultLibFilePath(options));
|
|
26
|
+
function document(name) {
|
|
27
|
+
if (documents.has(virtual(name))) return documents.get(virtual(name));
|
|
28
|
+
const text = originals.get(name) ?? ts.sys.readFile(name);
|
|
29
|
+
if (text === undefined) return null;
|
|
30
|
+
let mapped;
|
|
31
|
+
try { mapped = compileMappedSource(text); }
|
|
32
|
+
catch (error) { syntaxErrors.push(syntaxDiagnostic(error, text, name)); mapped = { code: 'export {};', lineMap: [1] }; }
|
|
33
|
+
const result = { ...mapped, source: text, filePath: name };
|
|
34
|
+
documents.set(virtual(name), result);
|
|
35
|
+
return result;
|
|
36
|
+
}
|
|
37
|
+
const isVirtual = name => name.endsWith('.hgs.ts') || originals.has(name.slice(0, -3));
|
|
38
|
+
const host = {
|
|
39
|
+
...base,
|
|
40
|
+
getCurrentDirectory: () => configPath ? path.dirname(configPath) : path.dirname(root),
|
|
41
|
+
getDefaultLibFileName: () => path.join(libDirectory, ts.getDefaultLibFileName(options)),
|
|
42
|
+
getDefaultLibLocation: () => libDirectory,
|
|
43
|
+
fileExists(name) { return isVirtual(name) ? originals.has(name.slice(0, -3)) || ts.sys.fileExists(name.slice(0, -3)) : base.fileExists(name); },
|
|
44
|
+
readFile(name) { return isVirtual(name) ? document(name.slice(0, -3))?.code : base.readFile(name); },
|
|
45
|
+
getSourceFile(name, languageVersion) {
|
|
46
|
+
const text = isVirtual(name) ? document(name.slice(0, -3))?.code : base.readFile(name);
|
|
47
|
+
if (text === undefined) return undefined;
|
|
48
|
+
if (name.startsWith(libDirectory) && name.endsWith('.d.ts')) {
|
|
49
|
+
const cached = libraryCache.get(name);
|
|
50
|
+
if (cached?.text === text) return cached;
|
|
51
|
+
const file = ts.createSourceFile(name, text, languageVersion, true); libraryCache.set(name, file); return file;
|
|
52
|
+
}
|
|
53
|
+
return ts.createSourceFile(name, text, languageVersion, true);
|
|
54
|
+
},
|
|
55
|
+
resolveModuleNames(names, containingFile) {
|
|
56
|
+
return names.map(name => {
|
|
57
|
+
if (name.endsWith('.hgs') && name.startsWith('.')) {
|
|
58
|
+
const original = path.resolve(path.dirname(containingFile), name);
|
|
59
|
+
if (originals.has(original) || ts.sys.fileExists(original)) return { resolvedFileName: virtual(original), extension: ts.Extension.Ts };
|
|
60
|
+
}
|
|
61
|
+
return ts.resolveModuleName(name, containingFile, options, host).resolvedModule;
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
const roots = settings.allFiles ? [...originals.keys()] : [root];
|
|
66
|
+
const program = ts.createProgram(roots.map(virtual), options, host);
|
|
67
|
+
const nativeDiagnostics = ts.getPreEmitDiagnostics(program).filter(d => {
|
|
68
|
+
if (![6133, 6196].includes(d.code) || !d.file || d.start === undefined) return true;
|
|
69
|
+
const doc = documents.get(d.file.fileName);
|
|
70
|
+
return !doc || Boolean(doc.lineMap[d.file.getLineAndCharacterOfPosition(d.start).line]);
|
|
71
|
+
});
|
|
72
|
+
const configErrors = [...converted.errors.filter(d => d.code !== 18003), ...(configRead?.error ? [configRead.error] : [])].map(d => ({ ...mapDiagnostic(d, documents), filePath: configPath ?? root }));
|
|
73
|
+
const diagnostics = [...configErrors, ...syntaxErrors, ...nativeDiagnostics.map(d => mapDiagnostic(d, documents))];
|
|
74
|
+
return { program, documents, diagnostics, nativeDiagnostics, virtualRoot: virtual(root), compiled: documents.get(virtual(root))?.code ?? '', hasErrors: diagnostics.some(d => d.severity === 'error') };
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export function checkSource(source, filePath = 'input.hgs', settings = {}) {
|
|
78
|
+
const { diagnostics, hasErrors } = analyzeSource(source, filePath, settings);
|
|
79
|
+
return { diagnostics, hasErrors };
|
|
80
|
+
}
|
package/dist/cli.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { compileFileToDefaultDestination, compileFile, lintFile, buildFile
|
|
3
|
-
import {
|
|
2
|
+
import { compileFileToDefaultDestination, compileFile, lintFile, buildFile } from './index.js';
|
|
3
|
+
import { fileURLToPath } from 'node:url';
|
|
4
4
|
import { spawn } from 'node:child_process';
|
|
5
5
|
import path from 'node:path';
|
|
6
6
|
|
|
@@ -15,7 +15,7 @@ function usage() {
|
|
|
15
15
|
--typescript 변환된 TypeScript 확인용 출력
|
|
16
16
|
-h, --help 도움말
|
|
17
17
|
|
|
18
|
-
|
|
18
|
+
run은 신뢰하는 로컬 코드만 실행하세요.`);
|
|
19
19
|
}
|
|
20
20
|
|
|
21
21
|
async function main() {
|
|
@@ -42,13 +42,9 @@ async function main() {
|
|
|
42
42
|
if (mode === 'run') {
|
|
43
43
|
if (inputs.length !== 1 || outDir || typescript || check) throw new Error('run에는 .hgs 파일 하나와 -- 뒤의 실행 인자만 지정하세요.');
|
|
44
44
|
const input = path.resolve(inputs[0]);
|
|
45
|
-
const
|
|
46
|
-
|
|
47
|
-
cwd: path.dirname(input), stdio: ['pipe', 'inherit', 'inherit']
|
|
45
|
+
const child = spawn(process.execPath, ['--import', fileURLToPath(new URL('./register.js', import.meta.url)), input, ...programArgs], {
|
|
46
|
+
cwd: path.dirname(input), stdio: 'inherit'
|
|
48
47
|
});
|
|
49
|
-
// Preserve imports relative to the source directory. Runtime needs Node.js 20+.
|
|
50
|
-
child.stdin.on('error', () => {});
|
|
51
|
-
child.stdin.end(javascript);
|
|
52
48
|
const exitCode = await new Promise((resolve, reject) => {
|
|
53
49
|
child.once('error', reject);
|
|
54
50
|
child.once('exit', (code, signal) => resolve(code ?? (signal ? 1 : 0)));
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import ts from 'typescript';
|
|
2
|
+
|
|
3
|
+
const hints = {
|
|
4
|
+
2304: '이름을 먼저 선언했는지 확인하세요.',
|
|
5
|
+
2307: '모듈 경로와 파일이 있는지 확인하세요.',
|
|
6
|
+
2322: '선언한 타입과 대입한 값의 타입을 확인하세요.',
|
|
7
|
+
2339: '이 값에 해당 속성이나 메서드가 있는지 확인하세요.',
|
|
8
|
+
2345: '함수에 전달한 값의 타입을 확인하세요.',
|
|
9
|
+
2554: '함수에 전달하는 인자의 개수를 확인하세요.',
|
|
10
|
+
7006: '공개 함수의 매개변수 타입을 명시하세요.'
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export class HangulScriptError extends Error {
|
|
14
|
+
constructor(diagnostics) {
|
|
15
|
+
super(formatSourceDiagnostics(diagnostics));
|
|
16
|
+
this.name = 'HangulScriptError';
|
|
17
|
+
this.diagnostics = diagnostics;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function syntaxDiagnostic(error, source, filePath) {
|
|
22
|
+
const lines = source.split(/\r?\n/);
|
|
23
|
+
const match = error.message.match(/^(\d+)행: /);
|
|
24
|
+
const line = match ? Number(match[1]) : Math.max(1, lines.findLastIndex(line => line.trim()) + 1);
|
|
25
|
+
const text = lines[line - 1] ?? '';
|
|
26
|
+
return { filePath, line, column: text.length - text.trimStart().length + 1, length: Math.max(1, text.trim().length), code: 'HGS1001', message: error.message.replace(/^\d+행: /, ''), severity: 'error' };
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function mapDiagnostic(diagnostic, documents) {
|
|
30
|
+
const file = diagnostic.file;
|
|
31
|
+
const position = file && diagnostic.start !== undefined ? file.getLineAndCharacterOfPosition(diagnostic.start) : { line: 0, character: 0 };
|
|
32
|
+
const document = file && documents.get(file.fileName);
|
|
33
|
+
let line = position.line + 1;
|
|
34
|
+
let column = position.character + 1;
|
|
35
|
+
let length = diagnostic.length ?? 1;
|
|
36
|
+
if (document) {
|
|
37
|
+
line = document.lineMap[position.line] || 1;
|
|
38
|
+
const original = document.source.split(/\r?\n/)[line - 1] ?? '';
|
|
39
|
+
const token = file.text.slice(diagnostic.start, diagnostic.start + length);
|
|
40
|
+
const generatedLine = file.text.split('\n')[position.line] ?? '';
|
|
41
|
+
const occurrence = token ? generatedLine.slice(0, position.character).split(token).length - 1 : 0;
|
|
42
|
+
let found = -1;
|
|
43
|
+
for (let i = 0; i <= occurrence; i++) {
|
|
44
|
+
const next = original.indexOf(token, found + 1);
|
|
45
|
+
if (next < 0) break;
|
|
46
|
+
found = next;
|
|
47
|
+
}
|
|
48
|
+
column = found >= 0 ? found + 1 : original.length - original.trimStart().length + 1;
|
|
49
|
+
length = Math.max(1, Math.min(length, original.length - column + 1));
|
|
50
|
+
}
|
|
51
|
+
const detail = ts.flattenDiagnosticMessageText(diagnostic.messageText, '\n');
|
|
52
|
+
return {
|
|
53
|
+
filePath: document?.filePath ?? file?.fileName ?? 'input.hgs', line, column, length,
|
|
54
|
+
code: diagnostic.code, message: hints[diagnostic.code] ? `${hints[diagnostic.code]}\n${detail}` : detail,
|
|
55
|
+
severity: diagnostic.category === ts.DiagnosticCategory.Error ? 'error' : 'warning'
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export function formatSourceDiagnostics(diagnostics) {
|
|
60
|
+
return diagnostics.map(d => `${d.filePath}:${d.line}:${d.column} [${typeof d.code === 'number' ? 'TS' : ''}${d.code}] ${d.message}`).join('\n');
|
|
61
|
+
}
|
package/dist/editor.d.ts
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { CheckSettings } from './index.js';
|
|
2
|
+
export interface EditorLocation { filePath: string; line: number; column: number; length: number; }
|
|
3
|
+
export interface EditorInspection { hover?: string; definitions: EditorLocation[]; completions: { name: string; detail: string }[]; }
|
|
4
|
+
/** One-based UTF-16 positions. Does not execute source. */
|
|
5
|
+
export function inspectSource(source: string, filePath: string, line: number, column: number, settings?: CheckSettings): EditorInspection;
|
package/dist/editor.js
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import ts from 'typescript';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { analyzeSource } from './checker.js';
|
|
4
|
+
import { mapDiagnostic } from './diagnostics.js';
|
|
5
|
+
|
|
6
|
+
/** One-based source positions; analysis only, never executes user code. */
|
|
7
|
+
export function inspectSource(source, filePath, line, column, settings = {}) {
|
|
8
|
+
const analysis = analyzeSource(source, filePath, settings);
|
|
9
|
+
const file = analysis.program.getSourceFile(analysis.virtualRoot);
|
|
10
|
+
const document = analysis.documents.get(analysis.virtualRoot);
|
|
11
|
+
if (!file || !document) return { completions: [], definitions: [] };
|
|
12
|
+
let target = file;
|
|
13
|
+
let matched;
|
|
14
|
+
const original = source.split(/\r?\n/)[line - 1] ?? '';
|
|
15
|
+
const visit = node => {
|
|
16
|
+
if (ts.isIdentifier(node)) {
|
|
17
|
+
const pos = file.getLineAndCharacterOfPosition(node.getStart(file));
|
|
18
|
+
if (document.lineMap[pos.line] === line) {
|
|
19
|
+
target = node;
|
|
20
|
+
const generatedLine = file.text.split('\n')[pos.line];
|
|
21
|
+
const occurrence = generatedLine.slice(0, pos.character).split(node.text).length - 1;
|
|
22
|
+
let found = -1;
|
|
23
|
+
for (let i=0; i<=occurrence; i++) { const next=original.indexOf(node.text,found+1); if(next<0) break; found=next; }
|
|
24
|
+
if (found >= 0 && column - 1 >= found && column - 1 <= found + node.text.length) matched = node;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
ts.forEachChild(node,visit);
|
|
28
|
+
};
|
|
29
|
+
visit(file);
|
|
30
|
+
const checker = analysis.program.getTypeChecker();
|
|
31
|
+
target = matched ?? target;
|
|
32
|
+
const completions = checker.getSymbolsInScope(target, ts.SymbolFlags.Value | ts.SymbolFlags.Type | ts.SymbolFlags.Alias)
|
|
33
|
+
.filter(symbol => symbol.declarations?.some(d => analysis.documents.has(d.getSourceFile().fileName) && !['화면연결'].includes(symbol.name)))
|
|
34
|
+
.map(symbol => ({ name: symbol.name, detail: checker.typeToString(checker.getTypeOfSymbolAtLocation(symbol,target)) }));
|
|
35
|
+
if (!matched) return { completions, definitions: [] };
|
|
36
|
+
let symbol = checker.getSymbolAtLocation(matched);
|
|
37
|
+
if (!symbol) return { completions, definitions: [] };
|
|
38
|
+
if (symbol.flags & ts.SymbolFlags.Alias) symbol = checker.getAliasedSymbol(symbol);
|
|
39
|
+
const type = checker.typeToString(checker.getTypeOfSymbolAtLocation(symbol,matched),matched,ts.TypeFormatFlags.NoTruncation);
|
|
40
|
+
const definitions = (symbol.declarations ?? []).flatMap(declaration => {
|
|
41
|
+
const name = declaration.name ?? declaration;
|
|
42
|
+
const sourceFile=name.getSourceFile();
|
|
43
|
+
const doc=analysis.documents.get(sourceFile.fileName);
|
|
44
|
+
const position=sourceFile.getLineAndCharacterOfPosition(name.getStart(sourceFile));
|
|
45
|
+
if (!doc || !doc.lineMap[position.line]) return [];
|
|
46
|
+
const location=mapDiagnostic({file:sourceFile,start:name.getStart(sourceFile),length:name.getWidth(sourceFile),code:0,messageText:'',category:ts.DiagnosticCategory.Message},analysis.documents);
|
|
47
|
+
return [{filePath:path.resolve(location.filePath),line:location.line,column:location.column,length:location.length}];
|
|
48
|
+
});
|
|
49
|
+
return { hover: `${symbol.name}: ${type}`, completions, definitions };
|
|
50
|
+
}
|
package/dist/emitter.js
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import ts from 'typescript';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { analyzeSource } from './checker.js';
|
|
4
|
+
import { HangulScriptError, mapDiagnostic } from './diagnostics.js';
|
|
5
|
+
|
|
6
|
+
// Keep JavaScript and declaration imports consistent with emitted filenames.
|
|
7
|
+
function rewriteImports(context) {
|
|
8
|
+
const visit = node => {
|
|
9
|
+
if (ts.isStringLiteral(node) && node.parent && node.text.endsWith('.hgs') &&
|
|
10
|
+
((ts.isImportDeclaration(node.parent) || ts.isExportDeclaration(node.parent)) && node.parent.moduleSpecifier === node ||
|
|
11
|
+
ts.isCallExpression(node.parent) && node.parent.expression.kind === ts.SyntaxKind.ImportKeyword && node.parent.arguments[0] === node)) {
|
|
12
|
+
return context.factory.createStringLiteral(node.text.replace(/\.hgs$/, '.js'));
|
|
13
|
+
}
|
|
14
|
+
// afterDeclarations nodes do not always carry parent links.
|
|
15
|
+
if (ts.isImportDeclaration(node) && ts.isStringLiteral(node.moduleSpecifier) && node.moduleSpecifier.text.endsWith('.hgs')) {
|
|
16
|
+
return context.factory.updateImportDeclaration(node, node.modifiers, node.importClause, context.factory.createStringLiteral(node.moduleSpecifier.text.replace(/\.hgs$/, '.js')), node.attributes);
|
|
17
|
+
}
|
|
18
|
+
if (ts.isExportDeclaration(node) && node.moduleSpecifier && ts.isStringLiteral(node.moduleSpecifier) && node.moduleSpecifier.text.endsWith('.hgs')) {
|
|
19
|
+
return context.factory.updateExportDeclaration(node, node.modifiers, node.isTypeOnly, node.exportClause, context.factory.createStringLiteral(node.moduleSpecifier.text.replace(/\.hgs$/, '.js')), node.attributes);
|
|
20
|
+
}
|
|
21
|
+
return ts.visitEachChild(node, visit, context);
|
|
22
|
+
};
|
|
23
|
+
return node => ts.visitNode(node, visit);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function emitSource(source, filePath) {
|
|
27
|
+
const result = analyzeSource(source, filePath);
|
|
28
|
+
if (result.hasErrors) throw new HangulScriptError(result.diagnostics);
|
|
29
|
+
const files = new Map();
|
|
30
|
+
const emitted = result.program.emit(undefined, (name, content) => files.set(name, content), undefined, undefined, { before: [rewriteImports], afterDeclarations: [rewriteImports] });
|
|
31
|
+
if (emitted.emitSkipped) throw new HangulScriptError(emitted.diagnostics.map(d => mapDiagnostic(d, result.documents)));
|
|
32
|
+
const modules = [...result.documents].map(([virtual, doc]) => ({
|
|
33
|
+
sourcePath: doc.filePath,
|
|
34
|
+
javascript: files.get(virtual.replace(/\.ts$/, '.js')) ?? '',
|
|
35
|
+
declaration: files.get(virtual.replace(/\.ts$/, '.d.ts')) ?? ''
|
|
36
|
+
}));
|
|
37
|
+
const root = modules.find(module => module.sourcePath === path.resolve(filePath));
|
|
38
|
+
return { javascript: root.javascript, declaration: root.declaration, modules };
|
|
39
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { compileSource } from './transformer.js';
|
|
2
|
+
|
|
3
|
+
/** Normalize indentation and whitespace without rewriting expressions or literals. */
|
|
4
|
+
export function formatSource(source, { tabSize = 4 } = {}) {
|
|
5
|
+
if (!Number.isInteger(tabSize) || tabSize < 1 || tabSize > 8) throw new RangeError('들여쓰기 폭은 1~8 사이의 정수여야 합니다.');
|
|
6
|
+
const eol = source.includes('\r\n') ? '\r\n' : '\n';
|
|
7
|
+
const expanded = source.replace(/^[ \t]*\t[ \t]*/gm, prefix => {
|
|
8
|
+
let width = 0;
|
|
9
|
+
for (const c of prefix) width += c === '\t' ? tabSize - width % tabSize : 1;
|
|
10
|
+
return ' '.repeat(width);
|
|
11
|
+
});
|
|
12
|
+
compileSource(expanded); // Refuse to guess the intended structure of invalid code.
|
|
13
|
+
const levels = [0];
|
|
14
|
+
const output = [];
|
|
15
|
+
for (const raw of expanded.split(/\r?\n/)) {
|
|
16
|
+
const text = raw.trim();
|
|
17
|
+
if (!text) { if (output.length && output.at(-1) !== '') output.push(''); continue; }
|
|
18
|
+
const indent = raw.length - raw.trimStart().length;
|
|
19
|
+
if (text.startsWith('#')) {
|
|
20
|
+
const depth = levels.filter(level => level < indent).length;
|
|
21
|
+
output.push(' '.repeat(depth * tabSize) + text); continue;
|
|
22
|
+
}
|
|
23
|
+
while (levels.at(-1) > indent) levels.pop();
|
|
24
|
+
if (indent > levels.at(-1)) levels.push(indent);
|
|
25
|
+
output.push(' '.repeat((levels.length - 1) * tabSize) + raw.trimStart().trimEnd());
|
|
26
|
+
}
|
|
27
|
+
while (output.at(-1) === '') output.pop();
|
|
28
|
+
return output.length ? output.join(eol) + eol : '';
|
|
29
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -5,6 +5,7 @@ export function compileFile(inputPath: string, outputPath: string): Promise<stri
|
|
|
5
5
|
export function compileFileToDefaultDestination(inputPath: string): Promise<string>;
|
|
6
6
|
export interface LintResult {
|
|
7
7
|
diagnostics: readonly ts.Diagnostic[];
|
|
8
|
+
sourceDiagnostics: readonly SourceDiagnostic[];
|
|
8
9
|
formattedDiagnostics: string;
|
|
9
10
|
hasErrors: boolean;
|
|
10
11
|
}
|
|
@@ -13,3 +14,11 @@ export function lintFile(inputPath: string, compilerOptions?: ts.CompilerOptions
|
|
|
13
14
|
export interface BuildResult { javascript: string; declaration: string; }
|
|
14
15
|
export function buildSource(source: string, filePath?: string): BuildResult;
|
|
15
16
|
export function buildFile(inputPath: string, outputDirectory?: string): Promise<BuildResult & { javascriptPath: string; declarationPath: string }>;
|
|
17
|
+
|
|
18
|
+
export interface SourceDiagnostic { filePath: string; line: number; column: number; length: number; code: number | string; message: string; severity: 'error' | 'warning'; }
|
|
19
|
+
export interface CheckSettings { files?: ReadonlyMap<string, string>; allFiles?: boolean; libDirectory?: string; compilerOptions?: ts.CompilerOptions; }
|
|
20
|
+
export function checkSource(source: string, filePath?: string, settings?: CheckSettings): { diagnostics: SourceDiagnostic[]; hasErrors: boolean };
|
|
21
|
+
export function formatSourceDiagnostics(diagnostics: readonly SourceDiagnostic[]): string;
|
|
22
|
+
export class HangulScriptError extends Error { diagnostics: SourceDiagnostic[]; constructor(diagnostics: SourceDiagnostic[]); }
|
|
23
|
+
|
|
24
|
+
export { formatSource } from './formatter.js';
|
package/dist/index.js
CHANGED
|
@@ -1,102 +1,11 @@
|
|
|
1
1
|
import { mkdir, readFile, writeFile } from 'node:fs/promises';
|
|
2
2
|
import path from 'node:path';
|
|
3
|
-
import { createRequire } from 'node:module';
|
|
4
3
|
import { compileSource } from './transformer.js';
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
function loadTypeScriptModule() {
|
|
11
|
-
if (tsModuleCache) {
|
|
12
|
-
return tsModuleCache;
|
|
13
|
-
}
|
|
14
|
-
let typescript;
|
|
15
|
-
try {
|
|
16
|
-
typescript = require('typescript');
|
|
17
|
-
} catch (initialError) {
|
|
18
|
-
const globalCandidate = path.join(process.execPath, '..', '..', 'lib', 'node_modules', 'typescript');
|
|
19
|
-
try {
|
|
20
|
-
typescript = require(globalCandidate);
|
|
21
|
-
} catch (fallbackError) {
|
|
22
|
-
throw new Error('TypeScript 모듈을 불러올 수 없습니다. Bun 또는 Node 환경에서 TypeScript를 설치해주세요.');
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
tsModuleCache = typescript.default ?? typescript;
|
|
26
|
-
return tsModuleCache;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
function getDefaultCompilerOptions(ts) {
|
|
30
|
-
if (!defaultCompilerOptions) {
|
|
31
|
-
defaultCompilerOptions = {
|
|
32
|
-
allowNonTsExtensions: true,
|
|
33
|
-
noEmit: true,
|
|
34
|
-
module: ts.ModuleKind.NodeNext,
|
|
35
|
-
moduleResolution: ts.ModuleResolutionKind.NodeNext,
|
|
36
|
-
target: ts.ScriptTarget.ES2020,
|
|
37
|
-
strict: true,
|
|
38
|
-
esModuleInterop: true
|
|
39
|
-
};
|
|
40
|
-
}
|
|
41
|
-
return defaultCompilerOptions;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
function createLintHost(ts, compilerOptions, filePath, sourceText) {
|
|
45
|
-
const resolvedFilePath = path.resolve(filePath);
|
|
46
|
-
const baseHost = ts.createCompilerHost(compilerOptions, true);
|
|
47
|
-
const sourceFile = ts.createSourceFile(
|
|
48
|
-
resolvedFilePath,
|
|
49
|
-
sourceText,
|
|
50
|
-
compilerOptions.target ?? ts.ScriptTarget.ES2020,
|
|
51
|
-
true,
|
|
52
|
-
ts.ScriptKind.TS
|
|
53
|
-
);
|
|
54
|
-
|
|
55
|
-
const getSourceFile = baseHost.getSourceFile.bind(baseHost);
|
|
56
|
-
const fileExists = baseHost.fileExists.bind(baseHost);
|
|
57
|
-
const readFile = baseHost.readFile.bind(baseHost);
|
|
58
|
-
|
|
59
|
-
baseHost.getSourceFile = (fileName, languageVersion, onError, shouldCreateNewSourceFile) => {
|
|
60
|
-
if (path.resolve(fileName) === resolvedFilePath) {
|
|
61
|
-
return shouldCreateNewSourceFile ? ts.createSourceFile(
|
|
62
|
-
resolvedFilePath,
|
|
63
|
-
sourceText,
|
|
64
|
-
languageVersion,
|
|
65
|
-
true,
|
|
66
|
-
ts.ScriptKind.TS
|
|
67
|
-
) : sourceFile;
|
|
68
|
-
}
|
|
69
|
-
return getSourceFile(fileName, languageVersion, onError, shouldCreateNewSourceFile);
|
|
70
|
-
};
|
|
71
|
-
|
|
72
|
-
baseHost.fileExists = (fileName) => {
|
|
73
|
-
if (path.resolve(fileName) === resolvedFilePath) {
|
|
74
|
-
return true;
|
|
75
|
-
}
|
|
76
|
-
return fileExists(fileName);
|
|
77
|
-
};
|
|
78
|
-
|
|
79
|
-
baseHost.readFile = (fileName) => {
|
|
80
|
-
if (path.resolve(fileName) === resolvedFilePath) {
|
|
81
|
-
return sourceText;
|
|
82
|
-
}
|
|
83
|
-
return readFile(fileName);
|
|
84
|
-
};
|
|
85
|
-
|
|
86
|
-
return baseHost;
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
function formatDiagnostics(ts, diagnostics) {
|
|
90
|
-
if (diagnostics.length === 0) {
|
|
91
|
-
return '';
|
|
92
|
-
}
|
|
93
|
-
const formatHost = {
|
|
94
|
-
getCanonicalFileName: (fileName) => fileName,
|
|
95
|
-
getCurrentDirectory: () => process.cwd(),
|
|
96
|
-
getNewLine: () => '\n'
|
|
97
|
-
};
|
|
98
|
-
return ts.formatDiagnosticsWithColorAndContext(diagnostics, formatHost);
|
|
99
|
-
}
|
|
4
|
+
import { analyzeSource } from './checker.js';
|
|
5
|
+
import { formatSourceDiagnostics } from './diagnostics.js';
|
|
6
|
+
import { emitSource } from './emitter.js';
|
|
7
|
+
export { checkSource } from './checker.js';
|
|
8
|
+
export { HangulScriptError, formatSourceDiagnostics } from './diagnostics.js';
|
|
100
9
|
|
|
101
10
|
export function compileString(source) {
|
|
102
11
|
return compileSource(source);
|
|
@@ -118,16 +27,13 @@ export async function compileFileToDefaultDestination(inputPath) {
|
|
|
118
27
|
}
|
|
119
28
|
|
|
120
29
|
export function lintSource(source, filePath = 'input.hgs', compilerOptions) {
|
|
121
|
-
|
|
122
|
-
const
|
|
123
|
-
const compiled = compileSource(source);
|
|
124
|
-
const host = createLintHost(ts, options, filePath, compiled);
|
|
125
|
-
const program = ts.createProgram([path.resolve(filePath)], options, host);
|
|
126
|
-
const diagnostics = ts.getPreEmitDiagnostics(program);
|
|
30
|
+
compileSource(source); // Keep syntax errors explicit for callers of the legacy lint API.
|
|
31
|
+
const result = analyzeSource(source, filePath, { compilerOptions });
|
|
127
32
|
return {
|
|
128
|
-
diagnostics,
|
|
129
|
-
|
|
130
|
-
|
|
33
|
+
diagnostics: result.nativeDiagnostics,
|
|
34
|
+
sourceDiagnostics: result.diagnostics,
|
|
35
|
+
formattedDiagnostics: formatSourceDiagnostics(result.diagnostics),
|
|
36
|
+
hasErrors: result.hasErrors
|
|
131
37
|
};
|
|
132
38
|
}
|
|
133
39
|
|
|
@@ -140,42 +46,30 @@ export { compileSource };
|
|
|
140
46
|
|
|
141
47
|
/** Compile through TypeScript once, emitting executable JS and declarations in memory. */
|
|
142
48
|
export function buildSource(source, filePath = 'input.hgs') {
|
|
143
|
-
const
|
|
144
|
-
|
|
145
|
-
const compiled = compileSource(source);
|
|
146
|
-
const options = {
|
|
147
|
-
target: ts.ScriptTarget.ES2022,
|
|
148
|
-
module: ts.ModuleKind.ESNext,
|
|
149
|
-
moduleResolution: ts.ModuleResolutionKind.Bundler,
|
|
150
|
-
strict: true,
|
|
151
|
-
declaration: true,
|
|
152
|
-
noEmitOnError: true,
|
|
153
|
-
skipLibCheck: true
|
|
154
|
-
};
|
|
155
|
-
const host = createLintHost(ts, options, virtualPath, compiled);
|
|
156
|
-
const files = new Map();
|
|
157
|
-
host.writeFile = (name, content) => files.set(name, content);
|
|
158
|
-
const program = ts.createProgram([virtualPath], options, host);
|
|
159
|
-
const diagnostics = ts.getPreEmitDiagnostics(program);
|
|
160
|
-
if (diagnostics.some(d => d.category === ts.DiagnosticCategory.Error)) {
|
|
161
|
-
throw new Error(formatDiagnostics(ts, diagnostics));
|
|
162
|
-
}
|
|
163
|
-
const emitted = program.emit();
|
|
164
|
-
if (emitted.emitSkipped) throw new Error(formatDiagnostics(ts, emitted.diagnostics) || '빌드에 실패했습니다.');
|
|
165
|
-
return {
|
|
166
|
-
javascript: files.get(virtualPath.replace(/\.ts$/, '.js')) ?? '',
|
|
167
|
-
declaration: files.get(virtualPath.replace(/\.ts$/, '.d.ts')) ?? ''
|
|
168
|
-
};
|
|
49
|
+
const { javascript, declaration } = emitSource(source, filePath);
|
|
50
|
+
return { javascript, declaration };
|
|
169
51
|
}
|
|
170
52
|
|
|
171
53
|
export async function buildFile(inputPath, outputDirectory = path.dirname(inputPath)) {
|
|
172
54
|
const source = await readFile(inputPath, 'utf8');
|
|
173
|
-
const
|
|
174
|
-
const
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
55
|
+
const { javascript, declaration, modules } = emitSource(source, inputPath);
|
|
56
|
+
const rootDirectory = path.dirname(path.resolve(inputPath));
|
|
57
|
+
const destinations = modules.map(module => {
|
|
58
|
+
const relative = path.relative(rootDirectory, module.sourcePath);
|
|
59
|
+
if (relative.startsWith('..' + path.sep) || path.isAbsolute(relative)) {
|
|
60
|
+
throw new Error('빌드 입력 파일의 폴더 밖에 있는 .hgs 모듈은 지원하지 않습니다. 공통 상위 폴더에 진입 파일을 두세요.');
|
|
61
|
+
}
|
|
62
|
+
const base = path.join(outputDirectory, relative.replace(/\.hgs$/, ''));
|
|
63
|
+
return { ...module, javascriptPath: base + '.js', declarationPath: base + '.d.ts' };
|
|
64
|
+
});
|
|
65
|
+
// Validate the complete graph before writing any output.
|
|
66
|
+
for (const module of destinations) {
|
|
67
|
+
await mkdir(path.dirname(module.javascriptPath), { recursive: true });
|
|
68
|
+
await writeFile(module.javascriptPath, module.javascript, 'utf8');
|
|
69
|
+
await writeFile(module.declarationPath, module.declaration, 'utf8');
|
|
70
|
+
}
|
|
71
|
+
const root = destinations.find(module => module.sourcePath === path.resolve(inputPath));
|
|
72
|
+
return { javascript, declaration, javascriptPath: root.javascriptPath, declarationPath: root.declarationPath };
|
|
181
73
|
}
|
|
74
|
+
|
|
75
|
+
export { formatSource } from './formatter.js';
|
package/dist/loader.js
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { readFile } from 'node:fs/promises';
|
|
2
|
+
import { fileURLToPath } from 'node:url';
|
|
3
|
+
import ts from 'typescript';
|
|
4
|
+
import { analyzeSource } from './checker.js';
|
|
5
|
+
import { HangulScriptError } from './diagnostics.js';
|
|
6
|
+
export async function load(url, context, nextLoad) {
|
|
7
|
+
if (url.startsWith('file:') && new URL(url).pathname.endsWith('.hgs')) {
|
|
8
|
+
const filePath=fileURLToPath(url);
|
|
9
|
+
const source=await readFile(filePath,'utf8');
|
|
10
|
+
const result=analyzeSource(source,filePath);
|
|
11
|
+
if(result.hasErrors) throw new HangulScriptError(result.diagnostics);
|
|
12
|
+
const compiled=ts.transpileModule(result.compiled,{compilerOptions:{module:ts.ModuleKind.ESNext,target:ts.ScriptTarget.ES2022}}).outputText;
|
|
13
|
+
return {format:'module',source:compiled,shortCircuit:true};
|
|
14
|
+
}
|
|
15
|
+
return nextLoad(url,context);
|
|
16
|
+
}
|
package/dist/register.js
ADDED
package/dist/runtime.js
CHANGED
package/dist/sentences.js
CHANGED
|
@@ -6,8 +6,12 @@ const identifier = /^[\p{L}_$][\p{L}\p{N}_$]*$/u;
|
|
|
6
6
|
const functionName = /^[\p{L}_$][\p{L}\p{N}_$]*(?:<.+>)?$/u;
|
|
7
7
|
const obsolete = /^(?:상수|변수|함수|반환|클래스|가져오기|내보내기|반복|비동기함수|스위치|선택|케이스|경우|인터페이스|열거형|시도|잡기|마침내|던지기|선언|추상)(?:\s|[({*])|^콘솔\.출력/u;
|
|
8
8
|
|
|
9
|
-
export function compileSentences(source, { includeRuntime = true } = {}) {
|
|
10
|
-
const output =
|
|
9
|
+
export function compileSentences(source, { includeRuntime = true, withMapping = false } = {}) {
|
|
10
|
+
const output = [];
|
|
11
|
+
const lineMap = [];
|
|
12
|
+
let currentLine = 0;
|
|
13
|
+
const push = code => { output.push(code); lineMap.push(...code.split('\n').map(() => currentLine)); };
|
|
14
|
+
if (includeRuntime) push(sentenceRuntime);
|
|
11
15
|
const frames = [{ indent: 0, kind: 'root' }];
|
|
12
16
|
let pending = null;
|
|
13
17
|
const lines = source.split(/\r?\n/);
|
|
@@ -19,6 +23,7 @@ export function compileSentences(source, { includeRuntime = true } = {}) {
|
|
|
19
23
|
return false;
|
|
20
24
|
};
|
|
21
25
|
for (let index = 0; index < lines.length; index++) {
|
|
26
|
+
currentLine = index + 1;
|
|
22
27
|
const raw = lines[index];
|
|
23
28
|
const text = raw.trim();
|
|
24
29
|
if (!text || text.startsWith('#')) continue;
|
|
@@ -33,12 +38,12 @@ export function compileSentences(source, { includeRuntime = true } = {}) {
|
|
|
33
38
|
let closed;
|
|
34
39
|
while (indent < frames.at(-1).indent) {
|
|
35
40
|
closed = frames.pop();
|
|
36
|
-
|
|
41
|
+
push(closed.kind === 'case' ? 'break; }' : '}');
|
|
37
42
|
}
|
|
38
43
|
if (indent !== frames.at(-1).indent) fail('이전 블록과 들여쓰기를 맞춰 주세요.');
|
|
39
44
|
const parent = frames.at(-1).kind;
|
|
40
|
-
const open = (code, kind) => {
|
|
41
|
-
const emit = code =>
|
|
45
|
+
const open = (code, kind) => { push(code + ' {'); pending = { kind }; };
|
|
46
|
+
const emit = code => push(code);
|
|
42
47
|
const exp = value => expression(value);
|
|
43
48
|
const top = () => { if (parent !== 'root') fail('가져오기·공개·타입 선언은 파일의 최상위에 작성하세요.'); };
|
|
44
49
|
let m;
|
|
@@ -132,7 +137,8 @@ export function compileSentences(source, { includeRuntime = true } = {}) {
|
|
|
132
137
|
while (frames.length > 1) {
|
|
133
138
|
const frame = frames.pop();
|
|
134
139
|
if (frame.kind === 'do') throw new SyntaxError('먼저 실행한다 블록 뒤에 반복 조건이 필요합니다.');
|
|
135
|
-
|
|
140
|
+
push(frame.kind === 'case' ? 'break; }' : '}');
|
|
136
141
|
}
|
|
137
|
-
|
|
142
|
+
const code = output.join('\n');
|
|
143
|
+
return withMapping ? { code, lineMap } : code;
|
|
138
144
|
}
|
package/dist/transformer.d.ts
CHANGED
|
@@ -1,2 +1,5 @@
|
|
|
1
1
|
/** Convert keyword-style or sentence-style .hgs source into TypeScript. */
|
|
2
2
|
export function compileSource(source: string): string;
|
|
3
|
+
|
|
4
|
+
export interface MappedSource { code: string; lineMap: number[]; }
|
|
5
|
+
export function compileMappedSource(source: string): MappedSource;
|
package/dist/transformer.js
CHANGED
|
@@ -5,3 +5,9 @@ export function compileSource(source) {
|
|
|
5
5
|
if (typeof source !== 'string') throw new TypeError('한글스크립트 소스는 문자열이어야 합니다.');
|
|
6
6
|
return compileSentences(source);
|
|
7
7
|
}
|
|
8
|
+
|
|
9
|
+
/** Generated TypeScript lines map to 1-based HGS lines; runtime lines map to zero. */
|
|
10
|
+
export function compileMappedSource(source) {
|
|
11
|
+
if (typeof source !== 'string') throw new TypeError('한글스크립트 소스는 문자열이어야 합니다.');
|
|
12
|
+
return compileSentences(source, { withMapping: true });
|
|
13
|
+
}
|
package/dist/vite.js
CHANGED
|
@@ -1,21 +1,28 @@
|
|
|
1
1
|
import ts from 'typescript';
|
|
2
|
-
import {
|
|
2
|
+
import { analyzeSource } from './checker.js';
|
|
3
|
+
import { HangulScriptError } from './diagnostics.js';
|
|
3
4
|
|
|
4
|
-
/**
|
|
5
|
+
/** Semantic checking is mandatory in both Vite development and production builds. */
|
|
5
6
|
export default function hangulscript() {
|
|
7
|
+
const files = new Map();
|
|
6
8
|
return {
|
|
7
9
|
name: 'hangulscript',
|
|
8
10
|
enforce: 'pre',
|
|
11
|
+
buildStart() { files.clear(); },
|
|
12
|
+
watchChange(id, change) { if (change.event === 'delete') files.delete(id.split('?')[0]); },
|
|
9
13
|
transform(source, id) {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
14
|
+
const filePath = id.split('?')[0];
|
|
15
|
+
if (!/\.hgs$/.test(filePath) || /[?&](?:raw|url)(?:[=&]|$)/.test(id)) return null;
|
|
16
|
+
files.set(filePath, source);
|
|
17
|
+
const checked = analyzeSource(source, filePath, { files, allFiles: true });
|
|
18
|
+
if (typeof this?.addWatchFile === 'function') for (const doc of checked.documents.values()) this.addWatchFile(doc.filePath);
|
|
19
|
+
if (checked.hasErrors) {
|
|
20
|
+
const error = new HangulScriptError(checked.diagnostics);
|
|
21
|
+
const first = checked.diagnostics.find(d => d.severity === 'error');
|
|
22
|
+
if (typeof this?.error === 'function') return this.error({ message: error.message, id: first.filePath, loc: { file: first.filePath, line: first.line, column: first.column - 1 } });
|
|
23
|
+
throw error;
|
|
24
|
+
}
|
|
25
|
+
const result = ts.transpileModule(checked.compiled, { compilerOptions: { target: ts.ScriptTarget.ES2022, module: ts.ModuleKind.ESNext } });
|
|
19
26
|
return { code: result.outputText, map: null };
|
|
20
27
|
}
|
|
21
28
|
};
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
# React·Vue에서 한글스크립트 사용하기
|
|
2
|
+
|
|
3
|
+
이 가이드는 `@uiwwsw/hangulscript@0.7.0` 문법과 공통 Vite 플러그인을 사용합니다.
|
|
4
|
+
화면과 상태는 React/Vue로 만들고 계산·조건·검증 함수를 `.hgs`에 작성합니다.
|
|
5
|
+
데모의 `숫자입력`·`버튼`·`보여주기`는 데모 UI 실행 환경의 API이며 React/Vue 컴포넌트로 자동 변환되지 않습니다.
|
|
6
|
+
|
|
7
|
+
## 완성된 예제를 먼저 실행하기
|
|
8
|
+
|
|
9
|
+
Node.js 24에서 검증했습니다. 예제의 Vite는 Node.js `^20.19.0 || >=22.12.0`을 요구합니다.
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
git clone https://github.com/uiwwsw/hangulscript.git
|
|
13
|
+
cd hangulscript/examples/react-vite
|
|
14
|
+
npm ci
|
|
15
|
+
npm run dev
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
Vue는 같은 저장소의 `examples/vue-vite`에서 `npm ci`와 `npm run dev`를 실행하세요.
|
|
19
|
+
터미널에 표시된 localhost 주소를 열고 가격 10000, 할인율 20을 입력한 뒤 **계산하기**를 누르면 결제 금액 8,000원이 표시됩니다.
|
|
20
|
+
개발 서버는 loopback에서 실행하며, 포트가 사용 중이면 터미널에 표시된 실제 주소를 사용하세요.
|
|
21
|
+
배포할 정적 파일은 각 예제에서 `npm run build`로 만들고 `npm run preview`로 확인합니다.
|
|
22
|
+
|
|
23
|
+
- [React 전체 소스](../examples/react-vite)
|
|
24
|
+
- [Vue 전체 소스](../examples/vue-vite)
|
|
25
|
+
|
|
26
|
+
## 기존 Vite 프로젝트에 설치하기
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
npm install --save-dev @uiwwsw/hangulscript@^0.7.0
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
React/Vue와 해당 Vite 플러그인은 기존 프로젝트에 설치돼 있다고 가정합니다.
|
|
33
|
+
새 프로젝트는 위의 완성된 예제를 사용할 수 있습니다.
|
|
34
|
+
|
|
35
|
+
React의 `vite.config.js`:
|
|
36
|
+
|
|
37
|
+
```js
|
|
38
|
+
import { defineConfig } from 'vite';
|
|
39
|
+
import react from '@vitejs/plugin-react';
|
|
40
|
+
import hangulscript from '@uiwwsw/hangulscript/vite';
|
|
41
|
+
|
|
42
|
+
export default defineConfig({ plugins: [hangulscript(), react()] });
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
Vue의 `vite.config.js`:
|
|
46
|
+
|
|
47
|
+
```js
|
|
48
|
+
import { defineConfig } from 'vite';
|
|
49
|
+
import vue from '@vitejs/plugin-vue';
|
|
50
|
+
import hangulscript from '@uiwwsw/hangulscript/vite';
|
|
51
|
+
|
|
52
|
+
export default defineConfig({ plugins: [hangulscript(), vue()] });
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## 공통 로직 작성하기
|
|
56
|
+
|
|
57
|
+
두 예제의 `src/discount.hgs`는 동일합니다. 함수와 조건을 한글 문장으로 작성하고 ESM `export`로 내보냅니다.
|
|
58
|
+
|
|
59
|
+
```text
|
|
60
|
+
결제금액(가격: 숫자, 할인율: 숫자)를 정의한다:
|
|
61
|
+
만약 !Number.isFinite(가격) || !Number.isFinite(할인율) || 가격 < 0 || 할인율 < 0 || 할인율 > 100 이라면:
|
|
62
|
+
돌려주기 "가격은 0 이상, 할인율은 0~100 사이로 입력하세요"
|
|
63
|
+
돌려주기 가격 - 반올림(가격 * 할인율 / 100)
|
|
64
|
+
|
|
65
|
+
결제금액을 공개한다
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
## React에서 호출하기
|
|
69
|
+
|
|
70
|
+
`src/main.jsx`에서 함수를 가져와 submit 이벤트에 연결합니다. 입력과 결과 상태는 React가 관리합니다.
|
|
71
|
+
|
|
72
|
+
```jsx
|
|
73
|
+
import { useState } from 'react';
|
|
74
|
+
import { createRoot } from 'react-dom/client';
|
|
75
|
+
import { 결제금액 } from './discount.hgs';
|
|
76
|
+
import './style.css';
|
|
77
|
+
|
|
78
|
+
function App() {
|
|
79
|
+
const [가격, 가격변경] = useState('10000');
|
|
80
|
+
const [할인율, 할인율변경] = useState('20');
|
|
81
|
+
const [결과, 결과변경] = useState(null);
|
|
82
|
+
function 계산(event) {
|
|
83
|
+
event.preventDefault();
|
|
84
|
+
결과변경(결제금액(Number(가격), Number(할인율)));
|
|
85
|
+
}
|
|
86
|
+
return <main>
|
|
87
|
+
<h1>React + 한글스크립트</h1>
|
|
88
|
+
<p>화면은 React, 계산과 검증은 discount.hgs에서 처리합니다.</p>
|
|
89
|
+
<form onSubmit={계산}>
|
|
90
|
+
<label>가격 (원)<input type="number" min="0" required value={가격} onChange={e => 가격변경(e.target.value)} /></label>
|
|
91
|
+
<label>할인율 (%)<input type="number" min="0" max="100" step="any" required value={할인율} onChange={e => 할인율변경(e.target.value)} /></label>
|
|
92
|
+
<button type="submit">계산하기</button>
|
|
93
|
+
</form>
|
|
94
|
+
<output aria-live="polite">{결과 === null ? '입력 후 계산하기를 눌러 주세요.' : typeof 결과 === 'number' ? `결제 금액: ${결과.toLocaleString('ko-KR')}원` : 결과}</output>
|
|
95
|
+
</main>;
|
|
96
|
+
}
|
|
97
|
+
createRoot(document.getElementById('app')).render(<App />);
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
## Vue에서 호출하기
|
|
101
|
+
|
|
102
|
+
`src/App.vue`에서 같은 함수를 가져와 submit 이벤트에 연결합니다. 반응형 상태는 Vue가 관리합니다.
|
|
103
|
+
|
|
104
|
+
```vue
|
|
105
|
+
<script setup>
|
|
106
|
+
import { ref } from 'vue';
|
|
107
|
+
import { 결제금액 } from './discount.hgs';
|
|
108
|
+
const 가격 = ref(10000);
|
|
109
|
+
const 할인율 = ref(20);
|
|
110
|
+
const 결과 = ref(null);
|
|
111
|
+
function 계산() {
|
|
112
|
+
결과.value = 결제금액(Number(가격.value), Number(할인율.value));
|
|
113
|
+
}
|
|
114
|
+
</script>
|
|
115
|
+
|
|
116
|
+
<template>
|
|
117
|
+
<main>
|
|
118
|
+
<h1>Vue + 한글스크립트</h1>
|
|
119
|
+
<p>화면은 Vue, 계산과 검증은 discount.hgs에서 처리합니다.</p>
|
|
120
|
+
<form @submit.prevent="계산">
|
|
121
|
+
<label>가격 (원)<input v-model="가격" type="number" min="0" required /></label>
|
|
122
|
+
<label>할인율 (%)<input v-model="할인율" type="number" min="0" max="100" step="any" required /></label>
|
|
123
|
+
<button type="submit">계산하기</button>
|
|
124
|
+
</form>
|
|
125
|
+
<output aria-live="polite">{{ 결과 === null ? '입력 후 계산하기를 눌러 주세요.' : typeof 결과 === 'number' ? `결제 금액: ${결과.toLocaleString('ko-KR')}원` : 결과 }}</output>
|
|
126
|
+
</main>
|
|
127
|
+
</template>
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
## TypeScript 프로젝트와 빌드 범위
|
|
131
|
+
|
|
132
|
+
| 방식 | 제공 범위 | 적합한 용도 |
|
|
133
|
+
| --- | --- | --- |
|
|
134
|
+
| Vite 플러그인으로 `.hgs` import | JavaScript 변환, Vite 개발 서버·빌드 연동 | React/Vue JavaScript 프로젝트 |
|
|
135
|
+
| `ksc build` 후 `.js` import | 타입 검사, JavaScript와 `.d.ts` 생성 | TypeScript 프로젝트·라이브러리 |
|
|
136
|
+
| 온라인 데모의 화면 함수 | 격리 실행, 입력창·버튼·결과 카드 | 설치 없는 도구 만들기·코드 공유 |
|
|
137
|
+
|
|
138
|
+
0.7.0부터 Vite 플러그인은 구문과 타입을 검사하고 오류가 있으면 빌드를 중단합니다. 선언 생성·디버깅용 원본 `.hgs` 소스맵은 제공하지 않습니다.
|
|
139
|
+
TypeScript에서 `.hgs` 파일을 직접 가져오면 모듈 타입을 찾지 못할 수 있습니다.
|
|
140
|
+
외부 TypeScript 소비자에게 선언을 제공하려면 다음처럼 빌드한 `.js`를 가져오세요. 인접한 `.d.ts`를 TypeScript가 읽습니다.
|
|
141
|
+
|
|
142
|
+
```bash
|
|
143
|
+
npx ksc build src/discount.hgs
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
```ts
|
|
147
|
+
import { 결제금액 } from './discount.js';
|
|
148
|
+
const 결과 = 결제금액(10000, 20); // number | string
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
이 방식은 `.hgs`를 수정할 때 다시 빌드해야 합니다. 명시적인 타입과 추론 가능한 값은 검사하며, `any` 값에 대한 검사는 제한됩니다.
|
|
152
|
+
Next.js·Webpack처럼 Vite를 사용하지 않는 환경에서도 미리 만든 `.js`·`.d.ts`를 가져오는 방식을 사용할 수 있습니다. 전용 Next.js·Webpack 플러그인은 제공하지 않습니다.
|
package/docs/support.md
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# 지원 범위와 호환성
|
|
2
|
+
|
|
3
|
+
한글스크립트는 명시된 한국어 문법을 JavaScript ES 모듈로 컴파일합니다. 임의의 자연어를 추측하지 않으며 별도 시작 표기는 사용하지 않습니다. 이 문서는 0.7.0 언어 패키지와 0.2.0 VS Code 확장의 지원 계약입니다.
|
|
4
|
+
|
|
5
|
+
| 환경 | 지원 내용 | 검증 |
|
|
6
|
+
| --- | --- | --- |
|
|
7
|
+
| Node.js 20.19 이상 | `ksc run`, JS + `.d.ts` 빌드, `.hgs` 모듈 import | Node 22·24 CI, 실행 통합 테스트 |
|
|
8
|
+
| React·Vue + Vite | `.hgs` import, 개발·프로덕션 타입 오류 차단 | React JSX·Vue SFC 예제 빌드 |
|
|
9
|
+
| VS Code 데스크톱 1.90 이상 | 문법 강조, 들여쓰기, 포맷, 진단, 타입 정보, 정의로 이동, 스코프 자동 완성 | 실제 Extension Host 통합 테스트 |
|
|
10
|
+
| 웹 데모 | 타입 검사, 격리 실행, 입력·버튼 UI, 공유 | 별도 브라우저 검사기 테스트 |
|
|
11
|
+
| JavaScript·TypeScript 소비자 | 생성된 `.js`와 `.d.ts` import | ESM 실행·선언 생성 테스트 |
|
|
12
|
+
|
|
13
|
+
## Node에서 사용하기
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
npm install @uiwwsw/hangulscript@^0.7.0
|
|
17
|
+
npx ksc run src/main.hgs
|
|
18
|
+
# 또는 Node의 등록 진입점으로 직접 로드
|
|
19
|
+
node --import @uiwwsw/hangulscript/register src/main.hgs
|
|
20
|
+
# 배포 파일 생성
|
|
21
|
+
npx ksc build src/main.hgs --out-dir dist
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
`ksc run`과 Node 등록 진입점은 `.hgs` 상대 import를 직접 읽고 검사합니다. 소스 옆에 생성물을 남기지 않습니다. 일반 Node 코드와 마찬가지로 파일·네트워크 권한을 가지므로 신뢰하는 코드에 사용하세요.
|
|
25
|
+
|
|
26
|
+
`ksc build`는 진입 파일과 그 폴더 아래의 `.hgs` 의존성을 함께 빌드하고 디렉터리 구조를 유지합니다. `.hgs` import는 `.js`로 바뀝니다. 진입 폴더 밖의 HGS 의존성은 오류로 처리하므로 공통 상위 폴더에 진입 파일을 두세요. 생성된 `.js`는 ESM이므로 소비 프로젝트 `package.json`에 `"type": "module"`을 설정하세요.
|
|
27
|
+
|
|
28
|
+
Vite가 아닌 환경에서 `.hgs` 파일을 바로 가져오려면 해당 로더가 필요합니다. 미리 생성한 `.js`·`.d.ts`는 일반 JS/TS 모듈로 사용할 수 있습니다. 상대 경로의 기존 JavaScript 파일·정적 자산은 HGS 빌드가 복사하지 않습니다.
|
|
29
|
+
|
|
30
|
+
## 타입과 오류
|
|
31
|
+
|
|
32
|
+
기본 검사는 strict 모드입니다. 가장 가까운 `tsconfig.json`의 `compilerOptions`와 `extends`를 읽습니다. 타입 해석을 위한 `types`, `typeRoots`, `paths`, `lib` 등을 사용할 수 있습니다. Node API 타입이 필요하면 프로젝트에 `@types/node`를 설치하고 `compilerOptions.types`에 `"node"`를 추가하세요. 파일 선택은 HGS 진입 파일과 import 그래프로 결정됩니다. 출력 형식은 ESM/ES2022, JS + 선언으로 고정하며 `noEmit`, `outDir` 등 출력 옵션은 CLI 설정이 우선합니다.
|
|
33
|
+
|
|
34
|
+
함수 매개변수에 타입을 적으면 잘못된 인자 타입·개수·존재하지 않는 속성을 검사합니다. `any`를 지정한 값은 엄격한 검사를 우회합니다. 외부 JavaScript 모듈은 대응하는 타입 선언이 있어야 정확히 검사됩니다. 브라우저 데모는 로컬 tsconfig·Node API·외부 모듈을 읽지 않습니다.
|
|
35
|
+
|
|
36
|
+
진단은 원본 HGS 행과 UTF-16 열을 사용합니다. 변환 과정에서 새로 생긴 토큰의 정확한 열을 찾을 수 없으면 해당 문장의 시작 위치를 사용합니다. 디버거용 소스맵과 실행 중 스택 추적의 원본 위치 변환은 아직 제공하지 않습니다.
|
|
37
|
+
|
|
38
|
+
## 편집기 기능과 배포
|
|
39
|
+
|
|
40
|
+
[VS Code 설치 및 기능 안내](../extensions/vscode/README.md)를 참고하세요. 확장은 컴파일러와 같은 저장소에서 관리하며 독립 버전으로 배포합니다. 편집기 검사는 Worker에서 실행하며 사용자 코드는 실행하지 않습니다. 자동 완성은 스코프의 이름과 스니펫을 제공합니다. 전체 속성 자동 완성, 자동 import, 이름 변경, 디버거, vscode.dev는 아직 지원하지 않습니다.
|
|
41
|
+
|
|
42
|
+
0.x에서는 문법의 호환성을 깨는 변경을 minor 버전에서만 진행하고 변경 내역·마이그레이션을 안내합니다. patch 버전은 호환되는 수정에 사용합니다. 1.0은 별도의 문법 동결과 호환성 검증을 거쳐 결정합니다. 사용자용 확장을 Marketplace에 게시하는 것과 Microsoft가 기본 내장 언어로 채택하는 것은 별개입니다.
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@uiwwsw/hangulscript",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "한글 문장으로 작성하고 JavaScript와 타입 선언으로 배포하는
|
|
3
|
+
"version": "0.7.0",
|
|
4
|
+
"description": "한글 문장으로 작성하고 JavaScript와 타입 선언으로 배포하는 프로그래밍 언어",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"main": "dist/index.js",
|
|
@@ -17,6 +17,15 @@
|
|
|
17
17
|
"./vite": {
|
|
18
18
|
"types": "./dist/vite.d.ts",
|
|
19
19
|
"import": "./dist/vite.js"
|
|
20
|
+
},
|
|
21
|
+
"./formatter": {
|
|
22
|
+
"types": "./dist/formatter.d.ts",
|
|
23
|
+
"import": "./dist/formatter.js"
|
|
24
|
+
},
|
|
25
|
+
"./register": "./dist/register.js",
|
|
26
|
+
"./editor": {
|
|
27
|
+
"types": "./dist/editor.d.ts",
|
|
28
|
+
"import": "./dist/editor.js"
|
|
20
29
|
}
|
|
21
30
|
},
|
|
22
31
|
"bin": {
|
|
@@ -27,14 +36,19 @@
|
|
|
27
36
|
"README.md",
|
|
28
37
|
"LANGUAGE.md",
|
|
29
38
|
"LICENSE",
|
|
30
|
-
"examples/*.hgs"
|
|
39
|
+
"examples/*.hgs",
|
|
40
|
+
"docs",
|
|
41
|
+
"CHANGELOG.md"
|
|
31
42
|
],
|
|
32
43
|
"scripts": {
|
|
33
44
|
"build": "node ./scripts/build.js",
|
|
34
45
|
"prepublishOnly": "npm test && npm run build",
|
|
35
46
|
"test": "node --test",
|
|
36
47
|
"build:site": "node scripts/build-site.js",
|
|
37
|
-
"benchmark": "node scripts/benchmark.js"
|
|
48
|
+
"benchmark": "node scripts/benchmark.js",
|
|
49
|
+
"build:vscode": "node scripts/build-vscode.js",
|
|
50
|
+
"package:vscode": "npm run build:vscode && cd extensions/vscode && vsce package --no-dependencies",
|
|
51
|
+
"test:vscode": "npm run build:vscode && node scripts/run-editor-qa.js"
|
|
38
52
|
},
|
|
39
53
|
"dependencies": {
|
|
40
54
|
"typescript": "^5.4.0"
|
|
@@ -55,12 +69,14 @@
|
|
|
55
69
|
},
|
|
56
70
|
"homepage": "https://github.com/uiwwsw/hangulscript#readme",
|
|
57
71
|
"engines": {
|
|
58
|
-
"node": ">=20"
|
|
72
|
+
"node": ">=20.19.0"
|
|
59
73
|
},
|
|
60
74
|
"publishConfig": {
|
|
61
75
|
"access": "public"
|
|
62
76
|
},
|
|
63
77
|
"devDependencies": {
|
|
78
|
+
"@vscode/test-electron": "^3.1.0",
|
|
79
|
+
"@vscode/vsce": "^4.0.0",
|
|
64
80
|
"esbuild": "^0.25.0"
|
|
65
81
|
},
|
|
66
82
|
"types": "dist/index.d.ts"
|