This commit is contained in:
Egor Aristov 2025-11-08 09:37:30 +03:00
parent 3d66a03e2e
commit 269682e4ab
Signed by: egor3f
GPG Key ID: 40482A264AAEC85F
121 changed files with 41688 additions and 0 deletions

5
.gitignore vendored
View File

@ -1 +1,6 @@
/.idea /.idea
/go.work
/go.work.sum
node_modules
*.js
/TODO.md

201
LICENSE Normal file
View File

@ -0,0 +1,201 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

66
README.md Normal file
View File

@ -0,0 +1,66 @@
# Readme draft
1. write code in language A
2. annotate it (see below)
3. launch `kitcom -src path/to/source.A -dest path/to/generated/file.B`
4. Use generated file to IPC
## Typescript:
Currently only whole classes are supported.
### Annotate:
```typescript
/**
* @kittenipc api
*/
class ClassName {}
```
### Usage:
```typescript
const localApi = new LocalAPI(); // LocalAPI is written by hand
const ipc = new ChildIPC(localApi);
const goApi = new RemoteAPI(ipc); // RemoteAPI is generated by kitcom
await ipc.start();
// work
await ipc.wait();
```
## Golang:
Currently only whole structs are supported
### Annotate
```go
// kittenipc:api
type StructName struct {
}
```
### Usage:
```go
localApi := LocalAPI{} // LocalAPI is written by hand
cmd := exec.Command(fmt.Sprintf("node %s", "path to compiled js"))
ipc, err := kittenipc.NewParent(cmd, &localApi)
remoteApi := RemoteAPI{Ipc: ipc} // RemoteAPI is generated by kitcom
if err != nil {
log.Panic(err)
}
if err := ipc.Start(); err != nil {
log.Panic(err)
}
// work
if err := kit.Wait(); err != nil {
log.Panic(err)
}
```
LocalAPI on one side is RemoteAPI on the other side
# Library status
Work in progress. No tests, no docs, code is not finished! Not everything is working yet. Code is partly crap.

7
example/Makefile Normal file
View File

@ -0,0 +1,7 @@
default:
ipc:
@kitcom -src golang -dest ts/src/remote.ts
@kitcom -src ts/src -dest golang/remote.go -pkg main
.PHONY: ipc

3
example/golang/go.mod Normal file
View File

@ -0,0 +1,3 @@
module efprojects.com/kitten-ipc/example/simple
go 1.25.1

54
example/golang/main.go Normal file
View File

@ -0,0 +1,54 @@
package main
import (
"fmt"
"log"
"os"
"os/exec"
"path"
kittenipc "efprojects.com/kitten-ipc"
)
// kittenipc:api
type GoIpcApi struct {
}
func (api GoIpcApi) Div(a int, b int) (int, error) {
if b == 0 {
return 0, fmt.Errorf("zero division")
}
return a / b, nil
}
func main() {
cwd, err := os.Getwd()
if err != nil {
log.Panic(err)
}
localApi := GoIpcApi{}
cmdStr := fmt.Sprintf("node %s", path.Join(cwd, "..", "ts/index.js"))
cmd := exec.Command(cmdStr)
ipc, err := kittenipc.NewParent(cmd, &localApi)
if err != nil {
log.Panic(err)
}
if err := ipc.Start(); err != nil {
log.Panic(err)
}
remoteApi := TsIpcApi{Ipc: ipc}
res, err := remoteApi.Div(10, 2)
if err != nil {
log.Panic(err)
}
log.Printf("remote call result = %v", res)
if err := ipc.Wait(); err != nil {
log.Panic(err)
}
}

25
example/golang/remote.go Normal file
View File

@ -0,0 +1,25 @@
// Code generated by kitcom. DO NOT EDIT.
package main
import (
kittenipc "efprojects.com/kitten-ipc"
"fmt"
)
type TsIpcApi struct {
Ipc kittenipc.Callable
}
func (t *TsIpcApi) Div(
a int, b int,
) (
int, error,
) {
results, err := t.Ipc.Call("TsIpcApi.Div", a, b)
if err != nil {
return 0, fmt.Errorf("call to TsIpcApi.Div failed: %w", err)
}
_ = results
return results[0].(int), nil
}

7
example/ts/dist/goapi.gen.d.ts vendored Normal file
View File

@ -0,0 +1,7 @@
import { ParentIPC, ChildIPC } from "../../lib/ts/lib";
export default class IpcApi {
private ipc;
constructor(ipc: ParentIPC | ChildIPC);
Div(a: number, b: number): Promise<number>;
}
//# sourceMappingURL=goapi.gen.d.ts.map

1
example/ts/dist/goapi.gen.d.ts.map vendored Normal file
View File

@ -0,0 +1 @@
{"version":3,"file":"goapi.gen.d.ts","sourceRoot":"","sources":["../src/goapi.gen.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAEvD,MAAM,CAAC,OAAO,OAAO,MAAM;IACzB,OAAO,CAAC,GAAG,CAAuB;gBAEtB,GAAG,EAAE,SAAS,GAAG,QAAQ;IAI/B,GAAG,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;CAGjD"}

1
example/ts/dist/goapi.gen.js.map vendored Normal file
View File

@ -0,0 +1 @@
{"version":3,"file":"goapi.gen.js","sourceRoot":"","sources":["../src/goapi.gen.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAEvD,MAAM,CAAC,OAAO,OAAO,MAAM;IACjB,GAAG,CAAuB;IAElC,YAAY,GAAyB;QACnC,IAAI,CAAC,GAAG,GAAG,GAAG,CAAC;IACjB,CAAC;IAED,KAAK,CAAC,GAAG,CAAC,CAAS,EAAE,CAAS;QAC5B,OAAO,MAAM,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,KAAK,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;IAC1C,CAAC;CACF"}

2
example/ts/dist/index.d.ts vendored Normal file
View File

@ -0,0 +1,2 @@
export {};
//# sourceMappingURL=index.d.ts.map

1
example/ts/dist/index.d.ts.map vendored Normal file
View File

@ -0,0 +1 @@
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":""}

1
example/ts/dist/index.js.map vendored Normal file
View File

@ -0,0 +1 @@
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,QAAQ,EAAC,MAAM,YAAY,CAAC;AACpC,OAAO,QAAQ,MAAM,gBAAgB,CAAC;AAEtC;;GAEG;AACH,MAAM,QAAQ;IACV,GAAG,CAAC,CAAS,EAAE,CAAS;QACpB,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC;YACV,MAAM,IAAI,KAAK,CAAC,kBAAkB,CAAC,CAAC;QACxC,CAAC;QACD,OAAO,CAAC,GAAG,CAAC,CAAC;IACjB,CAAC;CACJ;AAED,KAAK,UAAU,IAAI;IACf,MAAM,QAAQ,GAAG,IAAI,QAAQ,EAAE,CAAC;IAChC,MAAM,GAAG,GAAG,IAAI,QAAQ,CAAC,QAAQ,CAAC,CAAC;IACnC,MAAM,KAAK,GAAG,IAAI,QAAQ,CAAC,GAAG,CAAC,CAAC;IAEhC,MAAM,GAAG,CAAC,KAAK,EAAE,CAAC;IAElB,OAAO,CAAC,GAAG,CAAC,QAAQ,MAAM,KAAK,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC;IAE9C,IAAI,CAAC;QACD,MAAM,KAAK,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC;IAC3B,CAAC;IAAC,OAAO,CAAC,EAAE,CAAC;QACT,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;IACrB,CAAC;IAED,MAAM,GAAG,CAAC,IAAI,EAAE,CAAC;AACrB,CAAC;AAED,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE;IACb,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;AACrB,CAAC,CAAC,CAAC"}

50
example/ts/package-lock.json generated Normal file
View File

@ -0,0 +1,50 @@
{
"name": "kitten-ipc-example",
"version": "1.0.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "kitten-ipc-example",
"version": "1.0.0",
"license": "Apache 2.0"
},
"../../../../../../opt/homebrew/lib/node_modules/list": {
"version": "2.0.19",
"extraneous": true,
"license": "MIT",
"devDependencies": {
"@types/chai": "^4.2.3",
"@types/mocha": "^5.2.7",
"@types/ramda": "^0.26.28",
"babel-cli": "^6.26.0",
"babel-core": "^6.26.3",
"babel-plugin-annotate-pure-calls": "^0.4.0",
"chai": "4.2.0",
"cherry-pick": "^0.5.0",
"codecov": "^3.6.1",
"fast-check": "^1.17.0",
"mocha": "^6.2.1",
"np": "^5.1.0",
"nyc": "^14.1.1",
"prettier": "1.18.2",
"proptest": "0.0.4",
"ramda": "0.26.1",
"source-map-support": "^0.5.13",
"ts-node": "^8.4.1",
"tslint": "^5.20.0",
"typescript": "^3.6.3"
}
},
"../../lib/ts": {
"name": "kitten-ipc",
"version": "1.0.0",
"extraneous": true,
"license": "Apache 2.0",
"dependencies": {
"@types/node": "^22.10.5",
"ts-events": "^3.4.1"
}
}
}
}

12
example/ts/package.json Normal file
View File

@ -0,0 +1,12 @@
{
"name": "kitten-ipc-example",
"version": "1.0.0",
"author": "Egor3f <ef@efprojects.com>",
"license": "Apache 2.0",
"description": "",
"main": "index.js",
"type": "module",
"scripts": {
"build": "tsc"
}
}

30
example/ts/src/index.ts Normal file
View File

@ -0,0 +1,30 @@
import {ChildIPC} from 'kitten-ipc';
import GoIpcApi from './remote.js';
/**
* @kittenipc api
*/
class TsIpcApi {
Div(a: number, b: number): number {
if (b === 0) {
throw new Error('zero division');
}
return a / b;
}
}
async function main() {
const localApi = new TsIpcApi();
const ipc = new ChildIPC(localApi);
const remoteApi = new GoIpcApi(ipc);
await ipc.start();
console.log(`remote call result = ${await remoteApi.Div(10, 2)}`);
await ipc.wait();
}
main().catch(e => {
console.trace(e);
});

15
example/ts/src/remote.ts Normal file
View File

@ -0,0 +1,15 @@
// Code generated by kitcom. DO NOT EDIT.
import { ParentIPC, ChildIPC } from "kitten-ipc";
export default class GoIpcApi {
protected ipc: ParentIPC | ChildIPC;
constructor(ipc: ParentIPC | ChildIPC) {
this.ipc = ipc;
}
async Div(a: number, b: number): Promise<number> {
const results = await this.ipc.call("GoIpcApi.Div", a, b);
return results[0] as number;
}
}

28
example/ts/tsconfig.json Normal file
View File

@ -0,0 +1,28 @@
{
"compilerOptions": {
"rootDir": "./src",
"outDir": "./dist",
"module": "nodenext",
"target": "esnext",
"sourceMap": true,
"declaration": true,
"declarationMap": true,
"noUncheckedIndexedAccess": true,
"exactOptionalPropertyTypes": true,
"strict": true,
"noImplicitReturns": true,
"noImplicitOverride": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true,
"noPropertyAccessFromIndexSignature": true,
"noUncheckedSideEffectImports": true,
"moduleDetection": "force",
"verbatimModuleSyntax": true,
}
}

56
kitcom/Makefile Normal file
View File

@ -0,0 +1,56 @@
SHELL := /bin/bash
tsgo_dir = ./internal/tsgo
my_package = efprojects.com/kitten-ipc/kitcom/internal/tsgo
default:
@echo "Please read Makefile for available targets"
vendor_tsgo:
@mkdir -p $(tsgo_dir)
@git clone --depth 1 https://github.com/microsoft/typescript-go
@find ./typescript-go/internal -type file -name "*.go" -exec sed -i -e 's!"github.com/microsoft/typescript-go/internal!"$(my_package)!g' {} \;
@cp -r ./typescript-go/internal/* $(tsgo_dir)
@rm -rf @rm -rf typescript-go
remove_tsgo_tests:
@find $(tsgo_dir) -name "*_test.go" -exec rm {} \;
# tree shaking. written in make just for "fun"
# caution: may cause eye hazard
remove_tsgo_unused:
@set -e ; \
dirs=`find $(tsgo_dir) -type d -mindepth 1 -maxdepth 1` ; \
nessesary_old="parser " ; \
nessesary="$$nessesary_old" ; \
while true; do \
for d in $$dirs; do \
pkg=`basename "$$d"` ; \
for usedIn in $$nessesary; do \
if grep -q -R "$(my_package)/$$pkg" "$(tsgo_dir)/$$usedIn" > /dev/null; then \
if [[ "$$nessesary" != *"$$pkg "* ]]; then \
nessesary="$$nessesary $$pkg " ; \
fi ; \
break ; \
fi ; \
done ; \
done ; \
if [[ "$$nessesary" == "$$nessesary_old" ]]; then \
break ; \
fi ; \
nessesary_old="$$nessesary" ; \
done ; \
for d in $$dirs; do \
pkg=`basename $$d` ; \
if [[ "$$nessesary" != *"$$pkg "* ]]; then \
echo "removing $$pkg" ; \
rm -rf $(tsgo_dir)/$$pkg ; \
fi ; \
done
.PHONY: vendor_tsgo remove_tsgo_tests remove_tsgo_unused

35
kitcom/api/api.go Normal file
View File

@ -0,0 +1,35 @@
package api
// todo check TInt size < 64
// todo check not float
type ValType int
const (
TInt ValType = 1
TString ValType = 2
TBool ValType = 3
TBlob ValType = 4
TArray ValType = 5
)
type Val struct {
Name string
Type ValType
Children []Val
}
type Method struct {
Name string
Params []Val
Ret []Val
}
type Endpoint struct {
Name string
Methods []Method
}
type Api struct {
Endpoints []Endpoint
}

9
kitcom/go.mod Normal file
View File

@ -0,0 +1,9 @@
module efprojects.com/kitten-ipc/kitcom
go 1.25.1
require (
github.com/go-json-experiment/json v0.0.0-20250910080747-cc2cfa0554c3
golang.org/x/sync v0.17.0
golang.org/x/text v0.29.0
)

6
kitcom/go.sum Normal file
View File

@ -0,0 +1,6 @@
github.com/go-json-experiment/json v0.0.0-20250910080747-cc2cfa0554c3 h1:02WINGfSX5w0Mn+F28UyRoSt9uvMhKguwWMlOAh6U/0=
github.com/go-json-experiment/json v0.0.0-20250910080747-cc2cfa0554c3/go.mod h1:uNVvRXArCGbZ508SxYYTC5v1JWoz2voff5pm25jU1Ok=
golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug=
golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
golang.org/x/text v0.29.0 h1:1neNs90w9YzJ9BocxfsQNHKuAT4pkghyXc4nhZ6sJvk=
golang.org/x/text v0.29.0/go.mod h1:7MhJOA9CD2qZyOKYazxdYMF85OwPdEr9jTtBpO7ydH4=

34
kitcom/golang/go_gen.tmpl Normal file
View File

@ -0,0 +1,34 @@
{{- /*gotype: efprojects.com/kitten-ipc/kitcom.goGenData*/ -}}
// Code generated by kitcom. DO NOT EDIT.
package {{.PkgName}}
import (
"fmt"
kittenipc "efprojects.com/kitten-ipc"
)
{{range $e := .Api.Endpoints}}
type {{.Name}} struct {
Ipc kittenipc.Callable
}
{{range $mtd := $e.Methods}}
func ({{$e.Name | receiver}} *{{$e.Name}}) {{$mtd.Name}}(
{{range $mtd.Params}}{{.Name}} {{.Type | typedef}}, {{end}}
) (
{{range $mtd.Ret}}{{.Type | typedef}}, {{end}}error,
) {
results, err := {{$e.Name | receiver}}.Ipc.Call("{{$e.Name}}.{{$mtd.Name}}"{{range $mtd.Params}}, {{.Name}}{{end}})
if err != nil {
return {{range $mtd.Ret}}{{.Type | zerovalue}}, {{end}} fmt.Errorf("call to {{$e.Name}}.{{$mtd.Name}} failed: %w", err)
}
_ = results
return {{range $idx, $ret := $mtd.Ret}}results[{{$idx}}].({{$ret.Type | typedef}}), {{end}}nil
}
{{end}}
{{end}}

92
kitcom/golang/gogen.go Normal file
View File

@ -0,0 +1,92 @@
package golang
import (
"bytes"
"fmt"
"go/format"
"os"
"strings"
"text/template"
"efprojects.com/kitten-ipc/kitcom/api"
_ "embed"
)
//go:embed go_gen.tmpl
var templateString string
type goGenData struct {
PkgName string
Api *api.Api
}
type GoApiGenerator struct {
PkgName string
}
func (g *GoApiGenerator) Generate(apis *api.Api, destFile string) error {
tplCtx := goGenData{
PkgName: g.PkgName,
Api: apis,
}
tpl := template.New("gogen")
tpl = tpl.Funcs(map[string]any{
"receiver": func(name string) string {
return strings.ToLower(name)[0:1]
},
"typedef": func(t api.ValType) (string, error) {
td, ok := map[api.ValType]string{
api.TInt: "int",
api.TString: "string",
api.TBool: "bool",
}[t]
if !ok {
return "", fmt.Errorf("cannot generate type %v", t)
}
return td, nil
},
"zerovalue": func(t api.ValType) (string, error) {
v, ok := map[api.ValType]string{
api.TInt: "0",
api.TString: `""`,
api.TBool: "false",
}[t]
if !ok {
return "", fmt.Errorf("cannot generate zero value for type %v", t)
}
return v, nil
},
})
tpl = template.Must(tpl.Parse(templateString))
var buf bytes.Buffer
if err := tpl.ExecuteTemplate(&buf, "gogen", tplCtx); err != nil {
return fmt.Errorf("execute template: %w", err)
}
if err := g.writeDest(destFile, buf.Bytes()); err != nil {
return fmt.Errorf("write file: %w", err)
}
return nil
}
func (g *GoApiGenerator) writeDest(destFile string, bytes []byte) error {
f, err := os.OpenFile(destFile, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644)
if err != nil {
return fmt.Errorf("open destination file: %w", err)
}
defer f.Close()
formatted, err := format.Source(bytes)
if err != nil {
return fmt.Errorf("format source: %w", err)
}
if _, err := f.Write(formatted); err != nil {
return fmt.Errorf("write formatted source: %w", err)
}
return nil
}

163
kitcom/golang/goparser.go Normal file
View File

@ -0,0 +1,163 @@
package golang
import (
"fmt"
"go/ast"
"go/parser"
"go/token"
"regexp"
"efprojects.com/kitten-ipc/kitcom/api"
)
var decorComment = regexp.MustCompile(`^//\s?kittenipc:api$`)
type GoApiParser struct {
files []string
}
func (g *GoApiParser) AddFile(path string) {
g.files = append(g.files, path)
}
func (g *GoApiParser) Parse() (*api.Api, error) {
var apis api.Api
for _, f := range g.files {
endpoints, err := g.parseFile(f)
if err != nil {
return nil, fmt.Errorf("parse file: %w", err)
}
apis.Endpoints = append(apis.Endpoints, endpoints...)
}
if len(apis.Endpoints) == 0 {
return nil, fmt.Errorf("no endpoints found")
}
return &apis, nil
}
func (g *GoApiParser) parseFile(sourceFile string) ([]api.Endpoint, error) {
var endpoints []api.Endpoint
fileSet := token.NewFileSet()
astFile, err := parser.ParseFile(fileSet, sourceFile, nil, parser.ParseComments|parser.SkipObjectResolution)
if err != nil {
return nil, fmt.Errorf("parse file: %w", err)
}
for _, decl := range astFile.Decls {
genDecl, ok := decl.(*ast.GenDecl)
if !ok {
continue
}
if genDecl.Doc == nil {
continue
}
// use only last comment. https://tip.golang.org/doc/comment#syntax
lastComment := genDecl.Doc.List[len(genDecl.Doc.List)-1]
if !decorComment.MatchString(lastComment.Text) {
continue
}
typeSpec, ok := genDecl.Specs[0].(*ast.TypeSpec)
if !ok {
continue
}
_, isStruct := typeSpec.Type.(*ast.StructType)
_, isIface := typeSpec.Type.(*ast.InterfaceType)
if !isStruct && !isIface {
continue
}
endpoints = append(endpoints, api.Endpoint{
Name: typeSpec.Name.Name,
})
}
if len(endpoints) == 0 {
return nil, nil
}
for _, decl := range astFile.Decls {
funcDecl, ok := decl.(*ast.FuncDecl)
if !ok {
continue
}
if !funcDecl.Name.IsExported() {
continue
}
if funcDecl.Recv == nil {
continue
}
reciever := funcDecl.Recv.List[0]
recvType := reciever.Type
star, isPointer := recvType.(*ast.StarExpr)
if isPointer {
recvType = star.X
}
recvIdent, ok := recvType.(*ast.Ident)
if !ok {
continue
}
for i, endpoint := range endpoints {
if recvIdent.Name == endpoint.Name {
var apiMethod api.Method
apiMethod.Name = funcDecl.Name.Name
for _, param := range funcDecl.Type.Params.List {
var apiPar api.Val
ident := param.Type.(*ast.Ident)
switch ident.Name {
case "int":
apiPar.Type = api.TInt
case "string":
apiPar.Type = api.TString
case "bool":
apiPar.Type = api.TBool
default:
return nil, fmt.Errorf("parameter type %s is not supported yet", ident.Name)
}
if len(param.Names) != 1 {
return nil, fmt.Errorf("all parameters in method %s should be named", apiMethod.Name)
}
apiPar.Name = param.Names[0].Name
apiMethod.Params = append(apiMethod.Params, apiPar)
}
for _, ret := range funcDecl.Type.Results.List {
var apiRet api.Val
ident := ret.Type.(*ast.Ident)
switch ident.Name {
case "int":
apiRet.Type = api.TInt
case "string":
apiRet.Type = api.TString
case "bool":
apiRet.Type = api.TBool
case "error":
// errors are processed other way
continue
default:
return nil, fmt.Errorf("return type %s is not supported yet", ident.Name)
}
if len(ret.Names) > 0 {
apiRet.Name = ret.Names[0].Name
}
apiMethod.Ret = append(apiMethod.Ret, apiRet)
}
endpoints[i].Methods = append(endpoints[i].Methods, apiMethod)
}
}
}
return endpoints, nil
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,35 @@
package ast
// CheckFlags
type CheckFlags uint32
const (
CheckFlagsNone CheckFlags = 0
CheckFlagsInstantiated CheckFlags = 1 << 0 // Instantiated symbol
CheckFlagsSyntheticProperty CheckFlags = 1 << 1 // Property in union or intersection type
CheckFlagsSyntheticMethod CheckFlags = 1 << 2 // Method in union or intersection type
CheckFlagsReadonly CheckFlags = 1 << 3 // Readonly transient symbol
CheckFlagsReadPartial CheckFlags = 1 << 4 // Synthetic property present in some but not all constituents
CheckFlagsWritePartial CheckFlags = 1 << 5 // Synthetic property present in some but only satisfied by an index signature in others
CheckFlagsHasNonUniformType CheckFlags = 1 << 6 // Synthetic property with non-uniform type in constituents
CheckFlagsHasLiteralType CheckFlags = 1 << 7 // Synthetic property with at least one literal type in constituents
CheckFlagsContainsPublic CheckFlags = 1 << 8 // Synthetic property with public constituent(s)
CheckFlagsContainsProtected CheckFlags = 1 << 9 // Synthetic property with protected constituent(s)
CheckFlagsContainsPrivate CheckFlags = 1 << 10 // Synthetic property with private constituent(s)
CheckFlagsContainsStatic CheckFlags = 1 << 11 // Synthetic property with static constituent(s)
CheckFlagsLate CheckFlags = 1 << 12 // Late-bound symbol for a computed property with a dynamic name
CheckFlagsReverseMapped CheckFlags = 1 << 13 // Property of reverse-inferred homomorphic mapped type
CheckFlagsOptionalParameter CheckFlags = 1 << 14 // Optional parameter
CheckFlagsRestParameter CheckFlags = 1 << 15 // Rest parameter
CheckFlagsDeferredType CheckFlags = 1 << 16 // Calculation of the type of this symbol is deferred due to processing costs, should be fetched with `getTypeOfSymbolWithDeferredType`
CheckFlagsHasNeverType CheckFlags = 1 << 17 // Synthetic property with at least one never type in constituents
CheckFlagsMapped CheckFlags = 1 << 18 // Property of mapped type
CheckFlagsStripOptional CheckFlags = 1 << 19 // Strip optionality in mapped property
CheckFlagsUnresolved CheckFlags = 1 << 20 // Unresolved type alias symbol
CheckFlagsIsDiscriminantComputed CheckFlags = 1 << 21 // IsDiscriminant flags has been computed
CheckFlagsIsDiscriminant CheckFlags = 1 << 22 // Discriminant property
CheckFlagsSynthetic = CheckFlagsSyntheticProperty | CheckFlagsSyntheticMethod
CheckFlagsNonUniformAndLiteral = CheckFlagsHasNonUniformType | CheckFlagsHasLiteralType
CheckFlagsPartial = CheckFlagsReadPartial | CheckFlagsWritePartial
)

View File

@ -0,0 +1,86 @@
package ast
import "efprojects.com/kitten-ipc/kitcom/internal/tsgo/core"
// Ideally, this would get cached on the node factory so there's only ever one set of closures made per factory
func getDeepCloneVisitor(f *NodeFactory, syntheticLocation bool) *NodeVisitor {
var visitor *NodeVisitor
visitor = NewNodeVisitor(
func(node *Node) *Node {
visited := visitor.VisitEachChild(node)
if visited != node {
if syntheticLocation {
visited.Loc = core.NewTextRange(-1, -1)
}
return visited
}
c := node.Clone(f) // forcibly clone leaf nodes, which will then cascade new nodes/arrays upwards via `update` calls
// In strada, `factory.cloneNode` was dynamic and did _not_ clone positions for any "special cases", meanwhile
// Node.Clone in corsa reliably uses `Update` calls for all nodes and so copies locations by default.
// Deep clones are done to copy a node across files, so here, we explicitly make the location range synthetic on all cloned nodes
if syntheticLocation {
c.Loc = core.NewTextRange(-1, -1)
}
return c
},
f,
NodeVisitorHooks{
VisitNodes: func(nodes *NodeList, v *NodeVisitor) *NodeList {
if nodes == nil {
return nil
}
visited := v.VisitNodes(nodes)
var newList *NodeList
if visited != nodes {
newList = visited
} else {
newList = nodes.Clone(v.Factory)
}
if syntheticLocation {
newList.Loc = core.NewTextRange(-1, -1)
if nodes.HasTrailingComma() {
newList.Nodes[len(newList.Nodes)-1].Loc = core.NewTextRange(-2, -2)
}
}
return newList
},
VisitModifiers: func(nodes *ModifierList, v *NodeVisitor) *ModifierList {
if nodes == nil {
return nil
}
visited := v.VisitModifiers(nodes)
var newList *ModifierList
if visited != nodes {
newList = visited
} else {
newList = nodes.Clone(v.Factory)
}
if syntheticLocation {
newList.Loc = core.NewTextRange(-1, -1)
if nodes.HasTrailingComma() {
newList.Nodes[len(newList.Nodes)-1].Loc = core.NewTextRange(-2, -2)
}
}
return newList
},
},
)
return visitor
}
func (f *NodeFactory) DeepCloneNode(node *Node) *Node {
return getDeepCloneVisitor(f, true /*syntheticLocation*/).VisitNode(node)
}
func (f *NodeFactory) DeepCloneReparse(node *Node) *Node {
if node != nil {
node = getDeepCloneVisitor(f, false /*syntheticLocation*/).VisitNode(node)
SetParentInChildren(node)
node.Flags |= NodeFlagsReparsed
}
return node
}
func (f *NodeFactory) DeepCloneReparseModifiers(modifiers *ModifierList) *ModifierList {
return getDeepCloneVisitor(f, false /*syntheticLocation*/).VisitModifiers(modifiers)
}

View File

@ -0,0 +1,272 @@
package ast
import (
"maps"
"slices"
"strings"
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/core"
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/diagnostics"
)
// Diagnostic
type Diagnostic struct {
file *SourceFile
loc core.TextRange
code int32
category diagnostics.Category
message string
messageChain []*Diagnostic
relatedInformation []*Diagnostic
reportsUnnecessary bool
reportsDeprecated bool
skippedOnNoEmit bool
}
func (d *Diagnostic) File() *SourceFile { return d.file }
func (d *Diagnostic) Pos() int { return d.loc.Pos() }
func (d *Diagnostic) End() int { return d.loc.End() }
func (d *Diagnostic) Len() int { return d.loc.Len() }
func (d *Diagnostic) Loc() core.TextRange { return d.loc }
func (d *Diagnostic) Code() int32 { return d.code }
func (d *Diagnostic) Category() diagnostics.Category { return d.category }
func (d *Diagnostic) Message() string { return d.message }
func (d *Diagnostic) MessageChain() []*Diagnostic { return d.messageChain }
func (d *Diagnostic) RelatedInformation() []*Diagnostic { return d.relatedInformation }
func (d *Diagnostic) ReportsUnnecessary() bool { return d.reportsUnnecessary }
func (d *Diagnostic) ReportsDeprecated() bool { return d.reportsDeprecated }
func (d *Diagnostic) SkippedOnNoEmit() bool { return d.skippedOnNoEmit }
func (d *Diagnostic) SetFile(file *SourceFile) { d.file = file }
func (d *Diagnostic) SetLocation(loc core.TextRange) { d.loc = loc }
func (d *Diagnostic) SetCategory(category diagnostics.Category) { d.category = category }
func (d *Diagnostic) SetSkippedOnNoEmit() { d.skippedOnNoEmit = true }
func (d *Diagnostic) SetMessageChain(messageChain []*Diagnostic) *Diagnostic {
d.messageChain = messageChain
return d
}
func (d *Diagnostic) AddMessageChain(messageChain *Diagnostic) *Diagnostic {
if messageChain != nil {
d.messageChain = append(d.messageChain, messageChain)
}
return d
}
func (d *Diagnostic) SetRelatedInfo(relatedInformation []*Diagnostic) *Diagnostic {
d.relatedInformation = relatedInformation
return d
}
func (d *Diagnostic) AddRelatedInfo(relatedInformation *Diagnostic) *Diagnostic {
if relatedInformation != nil {
d.relatedInformation = append(d.relatedInformation, relatedInformation)
}
return d
}
func (d *Diagnostic) Clone() *Diagnostic {
result := *d
return &result
}
func NewDiagnosticWith(
file *SourceFile,
loc core.TextRange,
code int32,
category diagnostics.Category,
message string,
messageChain []*Diagnostic,
relatedInformation []*Diagnostic,
reportsUnnecessary bool,
reportsDeprecated bool,
skippedOnNoEmit bool,
) *Diagnostic {
return &Diagnostic{
file: file,
loc: loc,
code: code,
category: category,
message: message,
messageChain: messageChain,
relatedInformation: relatedInformation,
reportsUnnecessary: reportsUnnecessary,
reportsDeprecated: reportsDeprecated,
skippedOnNoEmit: skippedOnNoEmit,
}
}
func NewDiagnostic(file *SourceFile, loc core.TextRange, message *diagnostics.Message, args ...any) *Diagnostic {
return &Diagnostic{
file: file,
loc: loc,
code: message.Code(),
category: message.Category(),
message: message.Format(args...),
reportsUnnecessary: message.ReportsUnnecessary(),
reportsDeprecated: message.ReportsDeprecated(),
}
}
func NewDiagnosticChain(chain *Diagnostic, message *diagnostics.Message, args ...any) *Diagnostic {
if chain != nil {
return NewDiagnostic(chain.file, chain.loc, message, args...).AddMessageChain(chain).SetRelatedInfo(chain.relatedInformation)
}
return NewDiagnostic(nil, core.TextRange{}, message, args...)
}
func NewCompilerDiagnostic(message *diagnostics.Message, args ...any) *Diagnostic {
return NewDiagnostic(nil, core.UndefinedTextRange(), message, args...)
}
type DiagnosticsCollection struct {
fileDiagnostics map[string][]*Diagnostic
nonFileDiagnostics []*Diagnostic
}
func (c *DiagnosticsCollection) Add(diagnostic *Diagnostic) {
if diagnostic.File() != nil {
fileName := diagnostic.File().FileName()
if c.fileDiagnostics == nil {
c.fileDiagnostics = make(map[string][]*Diagnostic)
}
c.fileDiagnostics[fileName] = core.InsertSorted(c.fileDiagnostics[fileName], diagnostic, CompareDiagnostics)
} else {
c.nonFileDiagnostics = core.InsertSorted(c.nonFileDiagnostics, diagnostic, CompareDiagnostics)
}
}
func (c *DiagnosticsCollection) Lookup(diagnostic *Diagnostic) *Diagnostic {
var diagnostics []*Diagnostic
if diagnostic.File() != nil {
diagnostics = c.fileDiagnostics[diagnostic.File().FileName()]
} else {
diagnostics = c.nonFileDiagnostics
}
if i, ok := slices.BinarySearchFunc(diagnostics, diagnostic, CompareDiagnostics); ok {
return diagnostics[i]
}
return nil
}
func (c *DiagnosticsCollection) GetGlobalDiagnostics() []*Diagnostic {
return c.nonFileDiagnostics
}
func (c *DiagnosticsCollection) GetDiagnosticsForFile(fileName string) []*Diagnostic {
return c.fileDiagnostics[fileName]
}
func (c *DiagnosticsCollection) GetDiagnostics() []*Diagnostic {
fileNames := slices.Collect(maps.Keys(c.fileDiagnostics))
slices.Sort(fileNames)
diagnostics := slices.Clip(c.nonFileDiagnostics)
for _, fileName := range fileNames {
diagnostics = append(diagnostics, c.fileDiagnostics[fileName]...)
}
return diagnostics
}
func getDiagnosticPath(d *Diagnostic) string {
if d.File() != nil {
return d.File().FileName()
}
return ""
}
func EqualDiagnostics(d1, d2 *Diagnostic) bool {
return EqualDiagnosticsNoRelatedInfo(d1, d2) &&
slices.EqualFunc(d1.RelatedInformation(), d2.RelatedInformation(), EqualDiagnostics)
}
func EqualDiagnosticsNoRelatedInfo(d1, d2 *Diagnostic) bool {
return getDiagnosticPath(d1) == getDiagnosticPath(d2) &&
d1.Loc() == d2.Loc() &&
d1.Code() == d2.Code() &&
d1.Message() == d2.Message() &&
slices.EqualFunc(d1.MessageChain(), d2.MessageChain(), equalMessageChain)
}
func equalMessageChain(c1, c2 *Diagnostic) bool {
return c1.Code() == c2.Code() &&
c1.Message() == c2.Message() &&
slices.EqualFunc(c1.MessageChain(), c2.MessageChain(), equalMessageChain)
}
func compareMessageChainSize(c1, c2 []*Diagnostic) int {
c := len(c2) - len(c1)
if c != 0 {
return c
}
for i := range c1 {
c = compareMessageChainSize(c1[i].MessageChain(), c2[i].MessageChain())
if c != 0 {
return c
}
}
return 0
}
func compareMessageChainContent(c1, c2 []*Diagnostic) int {
for i := range c1 {
c := strings.Compare(c1[i].Message(), c2[i].Message())
if c != 0 {
return c
}
if c1[i].MessageChain() != nil {
c = compareMessageChainContent(c1[i].MessageChain(), c2[i].MessageChain())
if c != 0 {
return c
}
}
}
return 0
}
func compareRelatedInfo(r1, r2 []*Diagnostic) int {
c := len(r2) - len(r1)
if c != 0 {
return c
}
for i := range r1 {
c = CompareDiagnostics(r1[i], r2[i])
if c != 0 {
return c
}
}
return 0
}
func CompareDiagnostics(d1, d2 *Diagnostic) int {
c := strings.Compare(getDiagnosticPath(d1), getDiagnosticPath(d2))
if c != 0 {
return c
}
c = d1.Loc().Pos() - d2.Loc().Pos()
if c != 0 {
return c
}
c = d1.Loc().End() - d2.Loc().End()
if c != 0 {
return c
}
c = int(d1.Code()) - int(d2.Code())
if c != 0 {
return c
}
c = strings.Compare(d1.Message(), d2.Message())
if c != 0 {
return c
}
c = compareMessageChainSize(d1.MessageChain(), d2.MessageChain())
if c != 0 {
return c
}
c = compareMessageChainContent(d1.MessageChain(), d2.MessageChain())
if c != 0 {
return c
}
return compareRelatedInfo(d1.RelatedInformation(), d2.RelatedInformation())
}

View File

@ -0,0 +1,75 @@
package ast
// FlowFlags
type FlowFlags uint32
const (
FlowFlagsUnreachable FlowFlags = 1 << 0 // Unreachable code
FlowFlagsStart FlowFlags = 1 << 1 // Start of flow graph
FlowFlagsBranchLabel FlowFlags = 1 << 2 // Non-looping junction
FlowFlagsLoopLabel FlowFlags = 1 << 3 // Looping junction
FlowFlagsAssignment FlowFlags = 1 << 4 // Assignment
FlowFlagsTrueCondition FlowFlags = 1 << 5 // Condition known to be true
FlowFlagsFalseCondition FlowFlags = 1 << 6 // Condition known to be false
FlowFlagsSwitchClause FlowFlags = 1 << 7 // Switch statement clause
FlowFlagsArrayMutation FlowFlags = 1 << 8 // Potential array mutation
FlowFlagsCall FlowFlags = 1 << 9 // Potential assertion call
FlowFlagsReduceLabel FlowFlags = 1 << 10 // Temporarily reduce antecedents of label
FlowFlagsReferenced FlowFlags = 1 << 11 // Referenced as antecedent once
FlowFlagsShared FlowFlags = 1 << 12 // Referenced as antecedent more than once
FlowFlagsLabel = FlowFlagsBranchLabel | FlowFlagsLoopLabel
FlowFlagsCondition = FlowFlagsTrueCondition | FlowFlagsFalseCondition
)
// FlowNode
type FlowNode struct {
Flags FlowFlags
Node *Node // Associated AST node
Antecedent *FlowNode // Antecedent for all but FlowLabel
Antecedents *FlowList // Linked list of antecedents for FlowLabel
}
type FlowList struct {
Flow *FlowNode
Next *FlowList
}
type FlowLabel = FlowNode
// FlowSwitchClauseData (synthetic AST node for FlowFlagsSwitchClause)
type FlowSwitchClauseData struct {
NodeBase
SwitchStatement *Node
ClauseStart int32 // Start index of case/default clause range
ClauseEnd int32 // End index of case/default clause range
}
func NewFlowSwitchClauseData(switchStatement *Node, clauseStart int, clauseEnd int) *Node {
node := &FlowSwitchClauseData{}
node.SwitchStatement = switchStatement
node.ClauseStart = int32(clauseStart)
node.ClauseEnd = int32(clauseEnd)
return newNode(KindUnknown, node, NodeFactoryHooks{})
}
func (node *FlowSwitchClauseData) IsEmpty() bool {
return node.ClauseStart == node.ClauseEnd
}
// FlowReduceLabelData (synthetic AST node for FlowFlagsReduceLabel)
type FlowReduceLabelData struct {
NodeBase
Target *FlowLabel // Target label
Antecedents *FlowList // Temporary antecedent list
}
func NewFlowReduceLabelData(target *FlowLabel, antecedents *FlowList) *Node {
node := &FlowReduceLabelData{}
node.Target = target
node.Antecedents = antecedents
return newNode(KindUnknown, node, NodeFactoryHooks{})
}

View File

@ -0,0 +1,6 @@
package ast
type (
NodeId uint64
SymbolId uint64
)

View File

@ -0,0 +1,430 @@
package ast
//go:generate go tool golang.org/x/tools/cmd/stringer -type=Kind -output=kind_stringer_generated.go
//go:generate go tool mvdan.cc/gofumpt -w kind_stringer_generated.go
type Kind int16
const (
KindUnknown Kind = iota
KindEndOfFile
KindSingleLineCommentTrivia
KindMultiLineCommentTrivia
KindNewLineTrivia
KindWhitespaceTrivia
KindConflictMarkerTrivia
KindNonTextFileMarkerTrivia
KindNumericLiteral
KindBigIntLiteral
KindStringLiteral
KindJsxText
KindJsxTextAllWhiteSpaces
KindRegularExpressionLiteral
KindNoSubstitutionTemplateLiteral
// Pseudo-literals
KindTemplateHead
KindTemplateMiddle
KindTemplateTail
// Punctuation
KindOpenBraceToken
KindCloseBraceToken
KindOpenParenToken
KindCloseParenToken
KindOpenBracketToken
KindCloseBracketToken
KindDotToken
KindDotDotDotToken
KindSemicolonToken
KindCommaToken
KindQuestionDotToken
KindLessThanToken
KindLessThanSlashToken
KindGreaterThanToken
KindLessThanEqualsToken
KindGreaterThanEqualsToken
KindEqualsEqualsToken
KindExclamationEqualsToken
KindEqualsEqualsEqualsToken
KindExclamationEqualsEqualsToken
KindEqualsGreaterThanToken
KindPlusToken
KindMinusToken
KindAsteriskToken
KindAsteriskAsteriskToken
KindSlashToken
KindPercentToken
KindPlusPlusToken
KindMinusMinusToken
KindLessThanLessThanToken
KindGreaterThanGreaterThanToken
KindGreaterThanGreaterThanGreaterThanToken
KindAmpersandToken
KindBarToken
KindCaretToken
KindExclamationToken
KindTildeToken
KindAmpersandAmpersandToken
KindBarBarToken
KindQuestionToken
KindColonToken
KindAtToken
KindQuestionQuestionToken
/** Only the JSDoc scanner produces BacktickToken. The normal scanner produces NoSubstitutionTemplateLiteral and related kinds. */
KindBacktickToken
/** Only the JSDoc scanner produces HashToken. The normal scanner produces PrivateIdentifier. */
KindHashToken
// Assignments
KindEqualsToken
KindPlusEqualsToken
KindMinusEqualsToken
KindAsteriskEqualsToken
KindAsteriskAsteriskEqualsToken
KindSlashEqualsToken
KindPercentEqualsToken
KindLessThanLessThanEqualsToken
KindGreaterThanGreaterThanEqualsToken
KindGreaterThanGreaterThanGreaterThanEqualsToken
KindAmpersandEqualsToken
KindBarEqualsToken
KindBarBarEqualsToken
KindAmpersandAmpersandEqualsToken
KindQuestionQuestionEqualsToken
KindCaretEqualsToken
// Identifiers and PrivateIdentifier
KindIdentifier
KindPrivateIdentifier
KindJSDocCommentTextToken
// Reserved words
KindBreakKeyword
KindCaseKeyword
KindCatchKeyword
KindClassKeyword
KindConstKeyword
KindContinueKeyword
KindDebuggerKeyword
KindDefaultKeyword
KindDeleteKeyword
KindDoKeyword
KindElseKeyword
KindEnumKeyword
KindExportKeyword
KindExtendsKeyword
KindFalseKeyword
KindFinallyKeyword
KindForKeyword
KindFunctionKeyword
KindIfKeyword
KindImportKeyword
KindInKeyword
KindInstanceOfKeyword
KindNewKeyword
KindNullKeyword
KindReturnKeyword
KindSuperKeyword
KindSwitchKeyword
KindThisKeyword
KindThrowKeyword
KindTrueKeyword
KindTryKeyword
KindTypeOfKeyword
KindVarKeyword
KindVoidKeyword
KindWhileKeyword
KindWithKeyword
// Strict mode reserved words
KindImplementsKeyword
KindInterfaceKeyword
KindLetKeyword
KindPackageKeyword
KindPrivateKeyword
KindProtectedKeyword
KindPublicKeyword
KindStaticKeyword
KindYieldKeyword
// Contextual keywords
KindAbstractKeyword
KindAccessorKeyword
KindAsKeyword
KindAssertsKeyword
KindAssertKeyword
KindAnyKeyword
KindAsyncKeyword
KindAwaitKeyword
KindBooleanKeyword
KindConstructorKeyword
KindDeclareKeyword
KindGetKeyword
KindImmediateKeyword
KindInferKeyword
KindIntrinsicKeyword
KindIsKeyword
KindKeyOfKeyword
KindModuleKeyword
KindNamespaceKeyword
KindNeverKeyword
KindOutKeyword
KindReadonlyKeyword
KindRequireKeyword
KindNumberKeyword
KindObjectKeyword
KindSatisfiesKeyword
KindSetKeyword
KindStringKeyword
KindSymbolKeyword
KindTypeKeyword
KindUndefinedKeyword
KindUniqueKeyword
KindUnknownKeyword
KindUsingKeyword
KindFromKeyword
KindGlobalKeyword
KindBigIntKeyword
KindOverrideKeyword
KindOfKeyword
KindDeferKeyword // LastKeyword and LastToken and LastContextualKeyword
// Parse tree nodes
// Names
KindQualifiedName
KindComputedPropertyName
// Signature elements
KindTypeParameter
KindParameter
KindDecorator
// TypeMember
KindPropertySignature
KindPropertyDeclaration
KindMethodSignature
KindMethodDeclaration
KindClassStaticBlockDeclaration
KindConstructor
KindGetAccessor
KindSetAccessor
KindCallSignature
KindConstructSignature
KindIndexSignature
// Type
KindTypePredicate
KindTypeReference
KindFunctionType
KindConstructorType
KindTypeQuery
KindTypeLiteral
KindArrayType
KindTupleType
KindOptionalType
KindRestType
KindUnionType
KindIntersectionType
KindConditionalType
KindInferType
KindParenthesizedType
KindThisType
KindTypeOperator
KindIndexedAccessType
KindMappedType
KindLiteralType
KindNamedTupleMember
KindTemplateLiteralType
KindTemplateLiteralTypeSpan
KindImportType
// Binding patterns
KindObjectBindingPattern
KindArrayBindingPattern
KindBindingElement
// Expression
KindArrayLiteralExpression
KindObjectLiteralExpression
KindPropertyAccessExpression
KindElementAccessExpression
KindCallExpression
KindNewExpression
KindTaggedTemplateExpression
KindTypeAssertionExpression
KindParenthesizedExpression
KindFunctionExpression
KindArrowFunction
KindDeleteExpression
KindTypeOfExpression
KindVoidExpression
KindAwaitExpression
KindPrefixUnaryExpression
KindPostfixUnaryExpression
KindBinaryExpression
KindConditionalExpression
KindTemplateExpression
KindYieldExpression
KindSpreadElement
KindClassExpression
KindOmittedExpression
KindExpressionWithTypeArguments
KindAsExpression
KindNonNullExpression
KindMetaProperty
KindSyntheticExpression
KindSatisfiesExpression
// Misc
KindTemplateSpan
KindSemicolonClassElement
// Element
KindBlock
KindEmptyStatement
KindVariableStatement
KindExpressionStatement
KindIfStatement
KindDoStatement
KindWhileStatement
KindForStatement
KindForInStatement
KindForOfStatement
KindContinueStatement
KindBreakStatement
KindReturnStatement
KindWithStatement
KindSwitchStatement
KindLabeledStatement
KindThrowStatement
KindTryStatement
KindDebuggerStatement
KindVariableDeclaration
KindVariableDeclarationList
KindFunctionDeclaration
KindClassDeclaration
KindInterfaceDeclaration
KindTypeAliasDeclaration
KindEnumDeclaration
KindModuleDeclaration
KindModuleBlock
KindCaseBlock
KindNamespaceExportDeclaration
KindImportEqualsDeclaration
KindImportDeclaration
KindImportClause
KindNamespaceImport
KindNamedImports
KindImportSpecifier
KindExportAssignment
KindExportDeclaration
KindNamedExports
KindNamespaceExport
KindExportSpecifier
KindMissingDeclaration
// Module references
KindExternalModuleReference
// JSX
KindJsxElement
KindJsxSelfClosingElement
KindJsxOpeningElement
KindJsxClosingElement
KindJsxFragment
KindJsxOpeningFragment
KindJsxClosingFragment
KindJsxAttribute
KindJsxAttributes
KindJsxSpreadAttribute
KindJsxExpression
KindJsxNamespacedName
// Clauses
KindCaseClause
KindDefaultClause
KindHeritageClause
KindCatchClause
// Import attributes
KindImportAttributes
KindImportAttribute
// Property assignments
KindPropertyAssignment
KindShorthandPropertyAssignment
KindSpreadAssignment
// Enum
KindEnumMember
// Top-level nodes
KindSourceFile
KindBundle
// JSDoc nodes
KindJSDocTypeExpression
KindJSDocNameReference
KindJSDocMemberName // C#p
KindJSDocAllType // The * type
KindJSDocNullableType
KindJSDocNonNullableType
KindJSDocOptionalType
KindJSDocVariadicType
KindJSDoc
KindJSDocText
KindJSDocTypeLiteral
KindJSDocSignature
KindJSDocLink
KindJSDocLinkCode
KindJSDocLinkPlain
KindJSDocTag
KindJSDocAugmentsTag
KindJSDocImplementsTag
KindJSDocDeprecatedTag
KindJSDocPublicTag
KindJSDocPrivateTag
KindJSDocProtectedTag
KindJSDocReadonlyTag
KindJSDocOverrideTag
KindJSDocCallbackTag
KindJSDocOverloadTag
KindJSDocParameterTag
KindJSDocReturnTag
KindJSDocThisTag
KindJSDocTypeTag
KindJSDocTemplateTag
KindJSDocTypedefTag
KindJSDocSeeTag
KindJSDocPropertyTag
KindJSDocSatisfiesTag
KindJSDocImportTag
// Synthesized list
KindSyntaxList
// Reparsed JS nodes
KindJSTypeAliasDeclaration
KindJSExportAssignment
KindCommonJSExport
KindJSImportDeclaration
// Transformation nodes
KindNotEmittedStatement
KindPartiallyEmittedExpression
KindCommaListExpression
KindSyntheticReferenceExpression
KindNotEmittedTypeElement
// Enum value count
KindCount
// Markers
KindFirstAssignment = KindEqualsToken
KindLastAssignment = KindCaretEqualsToken
KindFirstCompoundAssignment = KindPlusEqualsToken
KindLastCompoundAssignment = KindCaretEqualsToken
KindFirstReservedWord = KindBreakKeyword
KindLastReservedWord = KindWithKeyword
KindFirstKeyword = KindBreakKeyword
KindLastKeyword = KindDeferKeyword
KindFirstFutureReservedWord = KindImplementsKeyword
KindLastFutureReservedWord = KindYieldKeyword
KindFirstTypeNode = KindTypePredicate
KindLastTypeNode = KindImportType
KindFirstPunctuation = KindOpenBraceToken
KindLastPunctuation = KindCaretEqualsToken
KindFirstToken = KindUnknown
KindLastToken = KindLastKeyword
KindFirstLiteralToken = KindNumericLiteral
KindLastLiteralToken = KindNoSubstitutionTemplateLiteral
KindFirstTemplateToken = KindNoSubstitutionTemplateLiteral
KindLastTemplateToken = KindTemplateTail
KindFirstBinaryOperator = KindLessThanToken
KindLastBinaryOperator = KindCaretEqualsToken
KindFirstStatement = KindVariableStatement
KindLastStatement = KindDebuggerStatement
KindFirstNode = KindQualifiedName
KindFirstJSDocNode = KindJSDocTypeExpression
KindLastJSDocNode = KindJSDocImportTag
KindFirstJSDocTagNode = KindJSDocTag
KindLastJSDocTagNode = KindJSDocImportTag
KindFirstContextualKeyword = KindAbstractKeyword
KindLastContextualKeyword = KindDeferKeyword
KindComment = KindSingleLineCommentTrivia | KindMultiLineCommentTrivia
KindFirstTriviaToken = KindSingleLineCommentTrivia
KindLastTriviaToken = KindConflictMarkerTrivia
)

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,53 @@
package ast
type ModifierFlags uint32
const (
ModifierFlagsNone ModifierFlags = 0
// Syntactic/JSDoc modifiers
ModifierFlagsPublic ModifierFlags = 1 << 0 // Property/Method
ModifierFlagsPrivate ModifierFlags = 1 << 1 // Property/Method
ModifierFlagsProtected ModifierFlags = 1 << 2 // Property/Method
ModifierFlagsReadonly ModifierFlags = 1 << 3 // Property/Method
ModifierFlagsOverride ModifierFlags = 1 << 4 // Override method
// Syntactic-only modifiers
ModifierFlagsExport ModifierFlags = 1 << 5 // Declarations
ModifierFlagsAbstract ModifierFlags = 1 << 6 // Class/Method/ConstructSignature
ModifierFlagsAmbient ModifierFlags = 1 << 7 // Declarations (declare keyword)
ModifierFlagsStatic ModifierFlags = 1 << 8 // Property/Method
ModifierFlagsAccessor ModifierFlags = 1 << 9 // Property
ModifierFlagsAsync ModifierFlags = 1 << 10 // Property/Method/Function
ModifierFlagsDefault ModifierFlags = 1 << 11 // Function/Class (export default declaration)
ModifierFlagsConst ModifierFlags = 1 << 12 // Const enum
ModifierFlagsIn ModifierFlags = 1 << 13 // Contravariance modifier
ModifierFlagsOut ModifierFlags = 1 << 14 // Covariance modifier
ModifierFlagsDecorator ModifierFlags = 1 << 15 // Contains a decorator
// JSDoc-only modifiers
ModifierFlagsDeprecated ModifierFlags = 1 << 16 // Deprecated tag
// Cache-only JSDoc-modifiers. Should match order of Syntactic/JSDoc modifiers, above.
ModifierFlagsJSDocPublic ModifierFlags = 1 << 23 // if this value changes, `selectEffectiveModifierFlags` must change accordingly
ModifierFlagsJSDocPrivate ModifierFlags = 1 << 24
ModifierFlagsJSDocProtected ModifierFlags = 1 << 25
ModifierFlagsJSDocReadonly ModifierFlags = 1 << 26
ModifierFlagsJSDocOverride ModifierFlags = 1 << 27
ModifierFlagsHasComputedJSDocModifiers ModifierFlags = 1 << 28 // Indicates the computed modifier flags include modifiers from JSDoc.
ModifierFlagsHasComputedFlags ModifierFlags = 1 << 29 // Modifier flags have been computed
ModifierFlagsSyntacticOrJSDocModifiers = ModifierFlagsPublic | ModifierFlagsPrivate | ModifierFlagsProtected | ModifierFlagsReadonly | ModifierFlagsOverride
ModifierFlagsSyntacticOnlyModifiers = ModifierFlagsExport | ModifierFlagsAmbient | ModifierFlagsAbstract | ModifierFlagsStatic | ModifierFlagsAccessor | ModifierFlagsAsync | ModifierFlagsDefault | ModifierFlagsConst | ModifierFlagsIn | ModifierFlagsOut | ModifierFlagsDecorator
ModifierFlagsSyntacticModifiers = ModifierFlagsSyntacticOrJSDocModifiers | ModifierFlagsSyntacticOnlyModifiers
ModifierFlagsJSDocCacheOnlyModifiers = ModifierFlagsJSDocPublic | ModifierFlagsJSDocPrivate | ModifierFlagsJSDocProtected | ModifierFlagsJSDocReadonly | ModifierFlagsJSDocOverride
ModifierFlagsJSDocOnlyModifiers = ModifierFlagsDeprecated
ModifierFlagsNonCacheOnlyModifiers = ModifierFlagsSyntacticOrJSDocModifiers | ModifierFlagsSyntacticOnlyModifiers | ModifierFlagsJSDocOnlyModifiers
ModifierFlagsAccessibilityModifier = ModifierFlagsPublic | ModifierFlagsPrivate | ModifierFlagsProtected
// Accessibility modifiers and 'readonly' can be attached to a parameter in a constructor to make it a property.
ModifierFlagsParameterPropertyModifier = ModifierFlagsAccessibilityModifier | ModifierFlagsReadonly | ModifierFlagsOverride
ModifierFlagsNonPublicAccessibilityModifier = ModifierFlagsPrivate | ModifierFlagsProtected
ModifierFlagsTypeScriptModifier = ModifierFlagsAmbient | ModifierFlagsPublic | ModifierFlagsPrivate | ModifierFlagsProtected | ModifierFlagsReadonly | ModifierFlagsAbstract | ModifierFlagsConst | ModifierFlagsOverride | ModifierFlagsIn | ModifierFlagsOut
ModifierFlagsExportDefault = ModifierFlagsExport | ModifierFlagsDefault
ModifierFlagsAll = ModifierFlagsExport | ModifierFlagsAmbient | ModifierFlagsPublic | ModifierFlagsPrivate | ModifierFlagsProtected | ModifierFlagsStatic | ModifierFlagsReadonly | ModifierFlagsAbstract | ModifierFlagsAccessor | ModifierFlagsAsync | ModifierFlagsDefault | ModifierFlagsConst | ModifierFlagsDeprecated | ModifierFlagsOverride | ModifierFlagsIn | ModifierFlagsOut | ModifierFlagsDecorator
ModifierFlagsModifier = ModifierFlagsAll & ^ModifierFlagsDecorator
ModifierFlagsJavaScript = ModifierFlagsExport | ModifierFlagsStatic | ModifierFlagsAccessor | ModifierFlagsAsync | ModifierFlagsDefault
)

View File

@ -0,0 +1,65 @@
package ast
type NodeFlags uint32
const (
NodeFlagsNone NodeFlags = 0
NodeFlagsLet NodeFlags = 1 << 0 // Variable declaration
NodeFlagsConst NodeFlags = 1 << 1 // Variable declaration
NodeFlagsUsing NodeFlags = 1 << 2 // Variable declaration
NodeFlagsReparsed NodeFlags = 1 << 3 // Node was synthesized during parsing
NodeFlagsSynthesized NodeFlags = 1 << 4 // Node was synthesized during transformation
NodeFlagsOptionalChain NodeFlags = 1 << 5 // Chained MemberExpression rooted to a pseudo-OptionalExpression
NodeFlagsExportContext NodeFlags = 1 << 6 // Export context (initialized by binding)
NodeFlagsContainsThis NodeFlags = 1 << 7 // Interface contains references to "this"
NodeFlagsHasImplicitReturn NodeFlags = 1 << 8 // If function implicitly returns on one of codepaths (initialized by binding)
NodeFlagsHasExplicitReturn NodeFlags = 1 << 9 // If function has explicit reachable return on one of codepaths (initialized by binding)
NodeFlagsDisallowInContext NodeFlags = 1 << 10 // If node was parsed in a context where 'in-expressions' are not allowed
NodeFlagsYieldContext NodeFlags = 1 << 11 // If node was parsed in the 'yield' context created when parsing a generator
NodeFlagsDecoratorContext NodeFlags = 1 << 12 // If node was parsed as part of a decorator
NodeFlagsAwaitContext NodeFlags = 1 << 13 // If node was parsed in the 'await' context created when parsing an async function
NodeFlagsDisallowConditionalTypesContext NodeFlags = 1 << 14 // If node was parsed in a context where conditional types are not allowed
NodeFlagsThisNodeHasError NodeFlags = 1 << 15 // If the parser encountered an error when parsing the code that created this node
NodeFlagsJavaScriptFile NodeFlags = 1 << 16 // If node was parsed in a JavaScript
NodeFlagsThisNodeOrAnySubNodesHasError NodeFlags = 1 << 17 // If this node or any of its children had an error
NodeFlagsHasAggregatedChildData NodeFlags = 1 << 18 // If we've computed data from children and cached it in this node
// These flags will be set when the parser encounters a dynamic import expression or 'import.meta' to avoid
// walking the tree if the flags are not set. However, these flags are just a approximation
// (hence why it's named "PossiblyContainsDynamicImport") because once set, the flags never get cleared.
// During editing, if a dynamic import is removed, incremental parsing will *NOT* clear this flag.
// This means that the tree will always be traversed during module resolution, or when looking for external module indicators.
// However, the removal operation should not occur often and in the case of the
// removal, it is likely that users will add the import anyway.
// The advantage of this approach is its simplicity. For the case of batch compilation,
// we guarantee that users won't have to pay the price of walking the tree if a dynamic import isn't used.
NodeFlagsPossiblyContainsDynamicImport NodeFlags = 1 << 19
NodeFlagsPossiblyContainsImportMeta NodeFlags = 1 << 20
NodeFlagsHasJSDoc NodeFlags = 1 << 21 // If node has preceding JSDoc comment(s)
NodeFlagsJSDoc NodeFlags = 1 << 22 // If node was parsed inside jsdoc
NodeFlagsAmbient NodeFlags = 1 << 23 // If node was inside an ambient context -- a declaration file, or inside something with the `declare` modifier.
NodeFlagsInWithStatement NodeFlags = 1 << 24 // If any ancestor of node was the `statement` of a WithStatement (not the `expression`)
NodeFlagsJsonFile NodeFlags = 1 << 25 // If node was parsed in a Json
NodeFlagsDeprecated NodeFlags = 1 << 26 // If has '@deprecated' JSDoc tag
NodeFlagsBlockScoped = NodeFlagsLet | NodeFlagsConst | NodeFlagsUsing
NodeFlagsConstant = NodeFlagsConst | NodeFlagsUsing
NodeFlagsAwaitUsing = NodeFlagsConst | NodeFlagsUsing // Variable declaration (NOTE: on a single node these flags would otherwise be mutually exclusive)
NodeFlagsReachabilityCheckFlags = NodeFlagsHasImplicitReturn | NodeFlagsHasExplicitReturn
// Parsing context flags
NodeFlagsContextFlags NodeFlags = NodeFlagsDisallowInContext | NodeFlagsDisallowConditionalTypesContext | NodeFlagsYieldContext | NodeFlagsDecoratorContext | NodeFlagsAwaitContext | NodeFlagsJavaScriptFile | NodeFlagsInWithStatement | NodeFlagsAmbient
// Exclude these flags when parsing a Type
NodeFlagsTypeExcludesFlags NodeFlags = NodeFlagsYieldContext | NodeFlagsAwaitContext
// Represents all flags that are potentially set once and
// never cleared on SourceFiles which get re-used in between incremental parses.
// See the comment above on `PossiblyContainsDynamicImport` and `PossiblyContainsImportMeta`.
NodeFlagsPermanentlySetIncrementalFlags NodeFlags = NodeFlagsPossiblyContainsDynamicImport | NodeFlagsPossiblyContainsImportMeta
// The following flags repurpose other NodeFlags as different meanings for Identifier nodes
NodeFlagsIdentifierHasExtendedUnicodeEscape NodeFlags = NodeFlagsContainsThis // Indicates whether the identifier contains an extended unicode escape sequence
)

View File

@ -0,0 +1,168 @@
package ast
import (
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/core"
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/tspath"
)
type JSDocParsingMode int
const (
JSDocParsingModeParseAll JSDocParsingMode = iota
JSDocParsingModeParseNone
JSDocParsingModeParseForTypeErrors
JSDocParsingModeParseForTypeInfo
)
type SourceFileParseOptions struct {
FileName string
Path tspath.Path
CompilerOptions core.SourceFileAffectingCompilerOptions
ExternalModuleIndicatorOptions ExternalModuleIndicatorOptions
JSDocParsingMode JSDocParsingMode
}
func GetSourceFileAffectingCompilerOptions(fileName string, options *core.CompilerOptions) core.SourceFileAffectingCompilerOptions {
// Declaration files are not parsed/bound differently depending on compiler options.
if tspath.IsDeclarationFileName(fileName) {
return core.SourceFileAffectingCompilerOptions{}
}
return options.SourceFileAffecting()
}
type ExternalModuleIndicatorOptions struct {
jsx bool
force bool
}
func GetExternalModuleIndicatorOptions(fileName string, options *core.CompilerOptions, metadata SourceFileMetaData) ExternalModuleIndicatorOptions {
if tspath.IsDeclarationFileName(fileName) {
return ExternalModuleIndicatorOptions{}
}
switch options.GetEmitModuleDetectionKind() {
case core.ModuleDetectionKindForce:
// All non-declaration files are modules, declaration files still do the usual isFileProbablyExternalModule
return ExternalModuleIndicatorOptions{force: true}
case core.ModuleDetectionKindLegacy:
// Files are modules if they have imports, exports, or import.meta
return ExternalModuleIndicatorOptions{}
case core.ModuleDetectionKindAuto:
// If module is nodenext or node16, all esm format files are modules
// If jsx is react-jsx or react-jsxdev then jsx tags force module-ness
// otherwise, the presence of import or export statments (or import.meta) implies module-ness
return ExternalModuleIndicatorOptions{
jsx: options.Jsx == core.JsxEmitReactJSX || options.Jsx == core.JsxEmitReactJSXDev,
force: isFileForcedToBeModuleByFormat(fileName, options, metadata),
}
default:
return ExternalModuleIndicatorOptions{}
}
}
var isFileForcedToBeModuleByFormatExtensions = []string{tspath.ExtensionCjs, tspath.ExtensionCts, tspath.ExtensionMjs, tspath.ExtensionMts}
func isFileForcedToBeModuleByFormat(fileName string, options *core.CompilerOptions, metadata SourceFileMetaData) bool {
// Excludes declaration files - they still require an explicit `export {}` or the like
// for back compat purposes. The only non-declaration files _not_ forced to be a module are `.js` files
// that aren't esm-mode (meaning not in a `type: module` scope).
if GetImpliedNodeFormatForEmitWorker(fileName, options.GetEmitModuleKind(), metadata) == core.ModuleKindESNext || tspath.FileExtensionIsOneOf(fileName, isFileForcedToBeModuleByFormatExtensions) {
return true
}
return false
}
func SetExternalModuleIndicator(file *SourceFile, opts ExternalModuleIndicatorOptions) {
file.ExternalModuleIndicator = getExternalModuleIndicator(file, opts)
}
func getExternalModuleIndicator(file *SourceFile, opts ExternalModuleIndicatorOptions) *Node {
if file.ScriptKind == core.ScriptKindJSON {
return nil
}
if node := isFileProbablyExternalModule(file); node != nil {
return node
}
if file.IsDeclarationFile {
return nil
}
if opts.jsx {
if node := isFileModuleFromUsingJSXTag(file); node != nil {
return node
}
}
if opts.force {
return file.AsNode()
}
return nil
}
func isFileProbablyExternalModule(sourceFile *SourceFile) *Node {
for _, statement := range sourceFile.Statements.Nodes {
if isAnExternalModuleIndicatorNode(statement) {
return statement
}
}
return getImportMetaIfNecessary(sourceFile)
}
func isAnExternalModuleIndicatorNode(node *Node) bool {
return HasSyntacticModifier(node, ModifierFlagsExport) ||
IsImportEqualsDeclaration(node) && IsExternalModuleReference(node.AsImportEqualsDeclaration().ModuleReference) ||
IsImportDeclaration(node) || IsExportAssignment(node) || IsExportDeclaration(node)
}
func getImportMetaIfNecessary(sourceFile *SourceFile) *Node {
if sourceFile.AsNode().Flags&NodeFlagsPossiblyContainsImportMeta != 0 {
return findChildNode(sourceFile.AsNode(), IsImportMeta)
}
return nil
}
func findChildNode(root *Node, check func(*Node) bool) *Node {
var result *Node
var visit func(*Node) bool
visit = func(node *Node) bool {
if check(node) {
result = node
return true
}
return node.ForEachChild(visit)
}
visit(root)
return result
}
func isFileModuleFromUsingJSXTag(file *SourceFile) *Node {
return walkTreeForJSXTags(file.AsNode())
}
// This is a somewhat unavoidable full tree walk to locate a JSX tag - `import.meta` requires the same,
// but we avoid that walk (or parts of it) if at all possible using the `PossiblyContainsImportMeta` node flag.
// Unfortunately, there's no `NodeFlag` space to do the same for JSX.
func walkTreeForJSXTags(node *Node) *Node {
var found *Node
var visitor func(node *Node) bool
visitor = func(node *Node) bool {
if found != nil {
return true
}
if node.SubtreeFacts()&SubtreeContainsJsx == 0 {
return false
}
if IsJsxOpeningElement(node) || IsJsxFragment(node) {
found = node
return true
}
return node.ForEachChild(visitor)
}
visitor(node)
return found
}

View File

@ -0,0 +1,714 @@
package ast
import (
"fmt"
)
type OperatorPrecedence int
const (
// Expression:
// AssignmentExpression
// Expression `,` AssignmentExpression
OperatorPrecedenceComma OperatorPrecedence = iota
// NOTE: `Spread` is higher than `Comma` due to how it is parsed in |ElementList|
// SpreadElement:
// `...` AssignmentExpression
OperatorPrecedenceSpread
// AssignmentExpression:
// ConditionalExpression
// YieldExpression
// ArrowFunction
// AsyncArrowFunction
// LeftHandSideExpression `=` AssignmentExpression
// LeftHandSideExpression AssignmentOperator AssignmentExpression
//
// NOTE: AssignmentExpression is broken down into several precedences due to the requirements
// of the parenthesizer rules.
// AssignmentExpression: YieldExpression
// YieldExpression:
// `yield`
// `yield` AssignmentExpression
// `yield` `*` AssignmentExpression
OperatorPrecedenceYield
// AssignmentExpression: LeftHandSideExpression `=` AssignmentExpression
// AssignmentExpression: LeftHandSideExpression AssignmentOperator AssignmentExpression
// AssignmentOperator: one of
// `*=` `/=` `%=` `+=` `-=` `<<=` `>>=` `>>>=` `&=` `^=` `|=` `**=`
OperatorPrecedenceAssignment
// NOTE: `Conditional` is considered higher than `Assignment` here, but in reality they have
// the same precedence.
// AssignmentExpression: ConditionalExpression
// ConditionalExpression:
// ShortCircuitExpression
// ShortCircuitExpression `?` AssignmentExpression `:` AssignmentExpression
OperatorPrecedenceConditional
// LogicalORExpression:
// LogicalANDExpression
// LogicalORExpression `||` LogicalANDExpression
OperatorPrecedenceLogicalOR
// LogicalANDExpression:
// BitwiseORExpression
// LogicalANDExprerssion `&&` BitwiseORExpression
OperatorPrecedenceLogicalAND
// BitwiseORExpression:
// BitwiseXORExpression
// BitwiseORExpression `|` BitwiseXORExpression
OperatorPrecedenceBitwiseOR
// BitwiseXORExpression:
// BitwiseANDExpression
// BitwiseXORExpression `^` BitwiseANDExpression
OperatorPrecedenceBitwiseXOR
// BitwiseANDExpression:
// EqualityExpression
// BitwiseANDExpression `&` EqualityExpression
OperatorPrecedenceBitwiseAND
// EqualityExpression:
// RelationalExpression
// EqualityExpression `==` RelationalExpression
// EqualityExpression `!=` RelationalExpression
// EqualityExpression `===` RelationalExpression
// EqualityExpression `!==` RelationalExpression
OperatorPrecedenceEquality
// RelationalExpression:
// ShiftExpression
// RelationalExpression `<` ShiftExpression
// RelationalExpression `>` ShiftExpression
// RelationalExpression `<=` ShiftExpression
// RelationalExpression `>=` ShiftExpression
// RelationalExpression `instanceof` ShiftExpression
// RelationalExpression `in` ShiftExpression
// [+TypeScript] RelationalExpression `as` Type
OperatorPrecedenceRelational
// ShiftExpression:
// AdditiveExpression
// ShiftExpression `<<` AdditiveExpression
// ShiftExpression `>>` AdditiveExpression
// ShiftExpression `>>>` AdditiveExpression
OperatorPrecedenceShift
// AdditiveExpression:
// MultiplicativeExpression
// AdditiveExpression `+` MultiplicativeExpression
// AdditiveExpression `-` MultiplicativeExpression
OperatorPrecedenceAdditive
// MultiplicativeExpression:
// ExponentiationExpression
// MultiplicativeExpression MultiplicativeOperator ExponentiationExpression
// MultiplicativeOperator: one of `*`, `/`, `%`
OperatorPrecedenceMultiplicative
// ExponentiationExpression:
// UnaryExpression
// UpdateExpression `**` ExponentiationExpression
OperatorPrecedenceExponentiation
// UnaryExpression:
// UpdateExpression
// `delete` UnaryExpression
// `void` UnaryExpression
// `typeof` UnaryExpression
// `+` UnaryExpression
// `-` UnaryExpression
// `~` UnaryExpression
// `!` UnaryExpression
// AwaitExpression
// UpdateExpression: // TODO: Do we need to investigate the precedence here?
// `++` UnaryExpression
// `--` UnaryExpression
OperatorPrecedenceUnary
// UpdateExpression:
// LeftHandSideExpression
// LeftHandSideExpression `++`
// LeftHandSideExpression `--`
OperatorPrecedenceUpdate
// LeftHandSideExpression:
// NewExpression
// NewExpression:
// MemberExpression
// `new` NewExpression
OperatorPrecedenceLeftHandSide
// LeftHandSideExpression:
// OptionalExpression
// OptionalExpression:
// MemberExpression OptionalChain
// CallExpression OptionalChain
// OptionalExpression OptionalChain
OperatorPrecedenceOptionalChain
// LeftHandSideExpression:
// CallExpression
// CallExpression:
// CoverCallExpressionAndAsyncArrowHead
// SuperCall
// ImportCall
// CallExpression Arguments
// CallExpression `[` Expression `]`
// CallExpression `.` IdentifierName
// CallExpression TemplateLiteral
// MemberExpression:
// PrimaryExpression
// MemberExpression `[` Expression `]`
// MemberExpression `.` IdentifierName
// MemberExpression TemplateLiteral
// SuperProperty
// MetaProperty
// `new` MemberExpression Arguments
OperatorPrecedenceMember
// TODO: JSXElement?
// PrimaryExpression:
// `this`
// IdentifierReference
// Literal
// ArrayLiteral
// ObjectLiteral
// FunctionExpression
// ClassExpression
// GeneratorExpression
// AsyncFunctionExpression
// AsyncGeneratorExpression
// RegularExpressionLiteral
// TemplateLiteral
OperatorPrecedencePrimary
// PrimaryExpression:
// CoverParenthesizedExpressionAndArrowParameterList
OperatorPrecedenceParentheses
OperatorPrecedenceLowest = OperatorPrecedenceComma
OperatorPrecedenceHighest = OperatorPrecedenceParentheses
OperatorPrecedenceDisallowComma = OperatorPrecedenceYield
// ShortCircuitExpression:
// LogicalORExpression
// CoalesceExpression
// CoalesceExpression:
// CoalesceExpressionHead `??` BitwiseORExpression
// CoalesceExpressionHead:
// CoalesceExpression
// BitwiseORExpression
OperatorPrecedenceCoalesce = OperatorPrecedenceLogicalOR
// -1 is lower than all other precedences. Returning it will cause binary expression
// parsing to stop.
OperatorPrecedenceInvalid OperatorPrecedence = -1
)
func getOperator(expression *Expression) Kind {
switch expression.Kind {
case KindBinaryExpression:
return expression.AsBinaryExpression().OperatorToken.Kind
case KindPrefixUnaryExpression:
return expression.AsPrefixUnaryExpression().Operator
case KindPostfixUnaryExpression:
return expression.AsPostfixUnaryExpression().Operator
default:
return expression.Kind
}
}
// Gets the precedence of an expression
func GetExpressionPrecedence(expression *Expression) OperatorPrecedence {
operator := getOperator(expression)
var flags OperatorPrecedenceFlags
if expression.Kind == KindNewExpression && expression.AsNewExpression().Arguments == nil {
flags = OperatorPrecedenceFlagsNewWithoutArguments
} else if IsOptionalChain(expression) {
flags = OperatorPrecedenceFlagsOptionalChain
}
return GetOperatorPrecedence(expression.Kind, operator, flags)
}
type OperatorPrecedenceFlags int
const (
OperatorPrecedenceFlagsNone OperatorPrecedenceFlags = 0
OperatorPrecedenceFlagsNewWithoutArguments OperatorPrecedenceFlags = 1 << 0
OperatorPrecedenceFlagsOptionalChain OperatorPrecedenceFlags = 1 << 1
)
// Gets the precedence of an operator
func GetOperatorPrecedence(nodeKind Kind, operatorKind Kind, flags OperatorPrecedenceFlags) OperatorPrecedence {
switch nodeKind {
case KindCommaListExpression:
return OperatorPrecedenceComma
case KindSpreadElement:
return OperatorPrecedenceSpread
case KindYieldExpression:
return OperatorPrecedenceYield
// !!! By necessity, this differs from the old compiler to better align with ParenthesizerRules. consider backporting
case KindArrowFunction:
return OperatorPrecedenceAssignment
case KindConditionalExpression:
return OperatorPrecedenceConditional
case KindBinaryExpression:
switch operatorKind {
case KindCommaToken:
return OperatorPrecedenceComma
case KindEqualsToken,
KindPlusEqualsToken,
KindMinusEqualsToken,
KindAsteriskAsteriskEqualsToken,
KindAsteriskEqualsToken,
KindSlashEqualsToken,
KindPercentEqualsToken,
KindLessThanLessThanEqualsToken,
KindGreaterThanGreaterThanEqualsToken,
KindGreaterThanGreaterThanGreaterThanEqualsToken,
KindAmpersandEqualsToken,
KindCaretEqualsToken,
KindBarEqualsToken,
KindBarBarEqualsToken,
KindAmpersandAmpersandEqualsToken,
KindQuestionQuestionEqualsToken:
return OperatorPrecedenceAssignment
default:
return GetBinaryOperatorPrecedence(operatorKind)
}
// TODO: Should prefix `++` and `--` be moved to the `Update` precedence?
case KindTypeAssertionExpression,
KindNonNullExpression,
KindPrefixUnaryExpression,
KindTypeOfExpression,
KindVoidExpression,
KindDeleteExpression,
KindAwaitExpression:
return OperatorPrecedenceUnary
case KindPostfixUnaryExpression:
return OperatorPrecedenceUpdate
// !!! By necessity, this differs from the old compiler to better align with ParenthesizerRules. consider backporting
case KindPropertyAccessExpression, KindElementAccessExpression:
if flags&OperatorPrecedenceFlagsOptionalChain != 0 {
return OperatorPrecedenceOptionalChain
}
return OperatorPrecedenceMember
case KindCallExpression:
if flags&OperatorPrecedenceFlagsOptionalChain != 0 {
return OperatorPrecedenceOptionalChain
}
return OperatorPrecedenceMember
// !!! By necessity, this differs from the old compiler to better align with ParenthesizerRules. consider backporting
case KindNewExpression:
if flags&OperatorPrecedenceFlagsNewWithoutArguments != 0 {
return OperatorPrecedenceLeftHandSide
}
return OperatorPrecedenceMember
// !!! By necessity, this differs from the old compiler to better align with ParenthesizerRules. consider backporting
case KindTaggedTemplateExpression, KindMetaProperty, KindExpressionWithTypeArguments:
return OperatorPrecedenceMember
case KindAsExpression,
KindSatisfiesExpression:
return OperatorPrecedenceRelational
case KindThisKeyword,
KindSuperKeyword,
KindImportKeyword,
KindIdentifier,
KindPrivateIdentifier,
KindNullKeyword,
KindTrueKeyword,
KindFalseKeyword,
KindNumericLiteral,
KindBigIntLiteral,
KindStringLiteral,
KindArrayLiteralExpression,
KindObjectLiteralExpression,
KindFunctionExpression,
KindClassExpression,
KindRegularExpressionLiteral,
KindNoSubstitutionTemplateLiteral,
KindTemplateExpression,
KindOmittedExpression,
KindJsxElement,
KindJsxSelfClosingElement,
KindJsxFragment:
return OperatorPrecedencePrimary
// !!! By necessity, this differs from the old compiler to support emit. consider backporting
case KindParenthesizedExpression:
return OperatorPrecedenceParentheses
default:
return OperatorPrecedenceInvalid
}
}
// Gets the precedence of a binary operator
func GetBinaryOperatorPrecedence(operatorKind Kind) OperatorPrecedence {
switch operatorKind {
case KindQuestionQuestionToken:
return OperatorPrecedenceCoalesce
case KindBarBarToken:
return OperatorPrecedenceLogicalOR
case KindAmpersandAmpersandToken:
return OperatorPrecedenceLogicalAND
case KindBarToken:
return OperatorPrecedenceBitwiseOR
case KindCaretToken:
return OperatorPrecedenceBitwiseXOR
case KindAmpersandToken:
return OperatorPrecedenceBitwiseAND
case KindEqualsEqualsToken, KindExclamationEqualsToken, KindEqualsEqualsEqualsToken, KindExclamationEqualsEqualsToken:
return OperatorPrecedenceEquality
case KindLessThanToken, KindGreaterThanToken, KindLessThanEqualsToken, KindGreaterThanEqualsToken,
KindInstanceOfKeyword, KindInKeyword, KindAsKeyword, KindSatisfiesKeyword:
return OperatorPrecedenceRelational
case KindLessThanLessThanToken, KindGreaterThanGreaterThanToken, KindGreaterThanGreaterThanGreaterThanToken:
return OperatorPrecedenceShift
case KindPlusToken, KindMinusToken:
return OperatorPrecedenceAdditive
case KindAsteriskToken, KindSlashToken, KindPercentToken:
return OperatorPrecedenceMultiplicative
case KindAsteriskAsteriskToken:
return OperatorPrecedenceExponentiation
}
// -1 is lower than all other precedences. Returning it will cause binary expression
// parsing to stop.
return OperatorPrecedenceInvalid
}
// Gets the leftmost expression of an expression, e.g. `a` in `a.b`, `a[b]`, `a++`, `a+b`, `a?b:c`, `a as B`, etc.
func GetLeftmostExpression(node *Expression, stopAtCallExpressions bool) *Expression {
for {
switch node.Kind {
case KindPostfixUnaryExpression:
node = node.AsPostfixUnaryExpression().Operand
continue
case KindBinaryExpression:
node = node.AsBinaryExpression().Left
continue
case KindConditionalExpression:
node = node.AsConditionalExpression().Condition
continue
case KindTaggedTemplateExpression:
node = node.AsTaggedTemplateExpression().Tag
continue
case KindCallExpression:
if stopAtCallExpressions {
return node
}
fallthrough
case KindAsExpression,
KindElementAccessExpression,
KindPropertyAccessExpression,
KindNonNullExpression,
KindPartiallyEmittedExpression,
KindSatisfiesExpression:
node = node.Expression()
continue
}
return node
}
}
type TypePrecedence int32
const (
// Conditional precedence (lowest)
//
// Type[Extends]:
// ConditionalType[?Extends]
//
// ConditionalType[Extends]:
// [~Extends] UnionType `extends` Type[+Extends] `?` Type[~Extends] `:` Type[~Extends]
//
TypePrecedenceConditional TypePrecedence = iota
// JSDoc precedence (optional and variadic types)
//
// JSDocType:
// `...`? Type `=`?
TypePrecedenceJSDoc
// Function precedence
//
// Type[Extends]:
// ConditionalType[?Extends]
// FunctionType[?Extends]
// ConstructorType[?Extends]
//
// ConditionalType[Extends]:
// UnionType
//
// FunctionType[Extends]:
// TypeParameters? ArrowParameters `=>` Type[?Extends]
//
// ConstructorType[Extends]:
// `abstract`? TypeParameters? ArrowParameters `=>` Type[?Extends]
//
TypePrecedenceFunction
// Union precedence
//
// UnionType:
// `|`? UnionTypeNoBar
//
// UnionTypeNoBar:
// IntersectionType
// UnionTypeNoBar `|` IntersectionType
//
TypePrecedenceUnion
// Intersection precedence
//
// IntersectionType:
// `&`? IntersectionTypeNoAmpersand
//
// IntersectionTypeNoAmpersand:
// TypeOperator
// IntersectionTypeNoAmpersand `&` TypeOperator
//
TypePrecedenceIntersection
// TypeOperator precedence
//
// TypeOperator:
// PostfixType
// InferType
// `keyof` TypeOperator
// `unique` TypeOperator
// `readonly` PostfixType
//
// InferType:
// `infer` BindingIdentifier
// `infer` BindingIdentifier `extends` Type[+Extends]
//
TypePrecedenceTypeOperator
// Postfix precedence
//
// PostfixType:
// NonArrayType
// OptionalType
// ArrayType
// IndexedAccessType
//
// OptionalType:
// PostfixType `?`
//
// ArrayType:
// PostfixType `[` `]`
//
// IndexedAccessType:
// PostfixType `[` Type[~Extends] `]`
//
TypePrecedencePostfix
// NonArray precedence (highest)
//
// NonArrayType:
// KeywordType
// LiteralType
// ThisType
// ImportType
// TypeQuery
// MappedType
// TypeLiteral
// TupleType
// ParenthesizedType
// TypePredicate
// TypeReference
// TemplateType
//
// KeywordType: one of
// `any` `unknown` `string` `number` `bigint`
// `symbol` `boolean` `undefined` `never` `object`
// `intrinsic` `void`
//
// LiteralType:
// StringLiteral
// NoSubstitutionTemplateLiteral
// NumericLiteral
// BigIntLiteral
// `-` NumericLiteral
// `-` BigIntLiteral
// `true`
// `false`
// `null`
//
// ThisType:
// `this`
//
// ImportType:
// `typeof`? `import` `(` Type[~Extends] `,`? `)` ImportTypeQualifier? TypeArguments?
// `typeof`? `import` `(` Type[~Extends] `,` ImportTypeAttributes `,`? `)` ImportTypeQualifier? TypeArguments?
//
// ImportTypeQualifier:
// `.` EntityName
//
// ImportTypeAttributes:
// `{` `with` `:` ImportAttributes `,`? `}`
//
// TypeQuery:
//
// MappedType:
// `{` MappedTypePrefix? MappedTypePropertyName MappedTypeSuffix? `:` Type[~Extends] `;` `}`
//
// MappedTypePrefix:
// `readonly`
// `+` `readonly`
// `-` `readonly`
//
// MappedTypePropertyName:
// `[` BindingIdentifier `in` Type[~Extends] `]`
// `[` BindingIdentifier `in` Type[~Extends] `as` Type[~Extends] `]`
//
// MappedTypeSuffix:
// `?`
// `+` `?`
// `-` `?`
//
// TypeLiteral:
// `{` TypeElementList `}`
//
// TypeElementList:
// [empty]
// TypeElementList TypeElement
//
// TypeElement:
// PropertySignature
// MethodSignature
// IndexSignature
// CallSignature
// ConstructSignature
//
// PropertySignature:
// PropertyName `?`? TypeAnnotation? `;`
//
// MethodSignature:
// PropertyName `?`? TypeParameters? `(` FormalParameterList `)` TypeAnnotation? `;`
// `get` PropertyName TypeParameters? `(` FormalParameterList `)` TypeAnnotation? `;` // GetAccessor
// `set` PropertyName TypeParameters? `(` FormalParameterList `)` TypeAnnotation? `;` // SetAccessor
//
// IndexSignature:
// `[` IdentifierName`]` TypeAnnotation `;`
//
// CallSignature:
// TypeParameters? `(` FormalParameterList `)` TypeAnnotation? `;`
//
// ConstructSignature:
// `new` TypeParameters? `(` FormalParameterList `)` TypeAnnotation? `;`
//
// TupleType:
// `[` `]`
// `[` NamedTupleElementTypes `,`? `]`
// `[` TupleElementTypes `,`? `]`
//
// NamedTupleElementTypes:
// NamedTupleMember
// NamedTupleElementTypes `,` NamedTupleMember
//
// NamedTupleMember:
// IdentifierName `?`? `:` Type[~Extends]
// `...` IdentifierName `:` Type[~Extends]
//
// TupleElementTypes:
// TupleElementType
// TupleElementTypes `,` TupleElementType
//
// TupleElementType:
// Type[~Extends]
// OptionalType
// RestType
//
// RestType:
// `...` Type[~Extends]
//
// ParenthesizedType:
// `(` Type[~Extends] `)`
//
// TypePredicate:
// `asserts`? TypePredicateParameterName
// `asserts`? TypePredicateParameterName `is` Type[~Extends]
//
// TypePredicateParameterName:
// `this`
// IdentifierReference
//
// TypeReference:
// EntityName TypeArguments?
//
// TemplateType:
// TemplateHead Type[~Extends] TemplateTypeSpans
//
// TemplateTypeSpans:
// TemplateTail
// TemplateTypeMiddleList TemplateTail
//
// TemplateTypeMiddleList:
// TemplateMiddle Type[~Extends]
// TemplateTypeMiddleList TemplateMiddle Type[~Extends]
//
// TypeArguments:
// `<` TypeArgumentList `,`? `>`
//
// TypeArgumentList:
// Type[~Extends]
// TypeArgumentList `,` Type[~Extends]
//
TypePrecedenceNonArray
TypePrecedenceLowest = TypePrecedenceConditional
TypePrecedenceHighest = TypePrecedenceNonArray
)
// Gets the precedence of a TypeNode
func GetTypeNodePrecedence(n *TypeNode) TypePrecedence {
switch n.Kind {
case KindConditionalType:
return TypePrecedenceConditional
case KindJSDocOptionalType, KindJSDocVariadicType:
return TypePrecedenceJSDoc
case KindFunctionType, KindConstructorType:
return TypePrecedenceFunction
case KindUnionType:
return TypePrecedenceUnion
case KindIntersectionType:
return TypePrecedenceIntersection
case KindTypeOperator:
return TypePrecedenceTypeOperator
case KindInferType:
if n.AsInferTypeNode().TypeParameter.AsTypeParameter().Constraint != nil {
// `infer T extends U` must be treated as FunctionType precedence as the `extends` clause eagerly consumes
// TypeNode
return TypePrecedenceFunction
}
return TypePrecedenceTypeOperator
case KindIndexedAccessType, KindArrayType, KindOptionalType:
return TypePrecedencePostfix
case KindTypeQuery:
// TypeQuery is actually a NonArrayType, but we treat it as the same
// precedence as PostfixType
return TypePrecedencePostfix
case KindAnyKeyword,
KindUnknownKeyword,
KindStringKeyword,
KindNumberKeyword,
KindBigIntKeyword,
KindSymbolKeyword,
KindBooleanKeyword,
KindUndefinedKeyword,
KindNeverKeyword,
KindObjectKeyword,
KindIntrinsicKeyword,
KindVoidKeyword,
KindJSDocAllType,
KindJSDocNullableType,
KindJSDocNonNullableType,
KindLiteralType,
KindTypePredicate,
KindTypeReference,
KindTypeLiteral,
KindTupleType,
KindRestType,
KindParenthesizedType,
KindThisType,
KindMappedType,
KindNamedTupleMember,
KindTemplateLiteralType,
KindImportType:
return TypePrecedenceNonArray
default:
panic(fmt.Sprintf("unhandled TypeNode: %v", n.Kind))
}
}

View File

@ -0,0 +1,131 @@
package ast
import (
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/core"
)
type SubtreeFacts int32
const (
// Facts
// - Flags used to indicate that a node or subtree contains syntax relevant to a specific transform
SubtreeContainsTypeScript SubtreeFacts = 1 << iota
SubtreeContainsJsx
SubtreeContainsESDecorators
SubtreeContainsUsing
SubtreeContainsClassStaticBlocks
SubtreeContainsESClassFields
SubtreeContainsLogicalAssignments
SubtreeContainsNullishCoalescing
SubtreeContainsOptionalChaining
SubtreeContainsMissingCatchClauseVariable
SubtreeContainsESObjectRestOrSpread // subtree has a `...` somewhere inside it, never cleared
SubtreeContainsForAwaitOrAsyncGenerator
SubtreeContainsAnyAwait
SubtreeContainsExponentiationOperator
// Markers
// - Flags used to indicate that a node or subtree contains a particular kind of syntax.
SubtreeContainsLexicalThis
SubtreeContainsLexicalSuper
SubtreeContainsRestOrSpread // marker on any `...` - cleared on binding pattern exit
SubtreeContainsObjectRestOrSpread // marker on any `{...x}` - cleared on most scope exits
SubtreeContainsAwait
SubtreeContainsDynamicImport
SubtreeContainsClassFields
SubtreeContainsDecorators
SubtreeContainsIdentifier
SubtreeFactsComputed // NOTE: This should always be last
SubtreeFactsNone SubtreeFacts = 0
// Aliases (unused, for documentation purposes only - correspond to combinations in transformers/estransforms/definitions.go)
SubtreeContainsESNext = SubtreeContainsESDecorators | SubtreeContainsUsing
SubtreeContainsES2022 = SubtreeContainsClassStaticBlocks | SubtreeContainsESClassFields
SubtreeContainsES2021 = SubtreeContainsLogicalAssignments
SubtreeContainsES2020 = SubtreeContainsNullishCoalescing | SubtreeContainsOptionalChaining
SubtreeContainsES2019 = SubtreeContainsMissingCatchClauseVariable
SubtreeContainsES2018 = SubtreeContainsESObjectRestOrSpread | SubtreeContainsForAwaitOrAsyncGenerator
SubtreeContainsES2017 = SubtreeContainsAnyAwait
SubtreeContainsES2016 = SubtreeContainsExponentiationOperator
// Scope Exclusions
// - Bitmasks that exclude flags from propagating out of a specific context
// into the subtree flags of their container.
SubtreeExclusionsNode = SubtreeFactsComputed
SubtreeExclusionsEraseable = ^SubtreeContainsTypeScript
SubtreeExclusionsOuterExpression = SubtreeExclusionsNode
SubtreeExclusionsPropertyAccess = SubtreeExclusionsNode
SubtreeExclusionsElementAccess = SubtreeExclusionsNode
SubtreeExclusionsArrowFunction = SubtreeExclusionsNode | SubtreeContainsAwait | SubtreeContainsObjectRestOrSpread
SubtreeExclusionsFunction = SubtreeExclusionsNode | SubtreeContainsLexicalThis | SubtreeContainsLexicalSuper | SubtreeContainsAwait | SubtreeContainsObjectRestOrSpread
SubtreeExclusionsConstructor = SubtreeExclusionsNode | SubtreeContainsLexicalThis | SubtreeContainsLexicalSuper | SubtreeContainsAwait | SubtreeContainsObjectRestOrSpread
SubtreeExclusionsMethod = SubtreeExclusionsNode | SubtreeContainsLexicalThis | SubtreeContainsLexicalSuper | SubtreeContainsAwait | SubtreeContainsObjectRestOrSpread
SubtreeExclusionsAccessor = SubtreeExclusionsNode | SubtreeContainsLexicalThis | SubtreeContainsLexicalSuper | SubtreeContainsAwait | SubtreeContainsObjectRestOrSpread
SubtreeExclusionsProperty = SubtreeExclusionsNode | SubtreeContainsLexicalThis | SubtreeContainsLexicalSuper
SubtreeExclusionsClass = SubtreeExclusionsNode
SubtreeExclusionsModule = SubtreeExclusionsNode | SubtreeContainsLexicalThis | SubtreeContainsLexicalSuper
SubtreeExclusionsObjectLiteral = SubtreeExclusionsNode | SubtreeContainsObjectRestOrSpread
SubtreeExclusionsArrayLiteral = SubtreeExclusionsNode
SubtreeExclusionsCall = SubtreeExclusionsNode
SubtreeExclusionsNew = SubtreeExclusionsNode
SubtreeExclusionsVariableDeclarationList = SubtreeExclusionsNode | SubtreeContainsObjectRestOrSpread
SubtreeExclusionsParameter = SubtreeExclusionsNode
SubtreeExclusionsCatchClause = SubtreeExclusionsNode | SubtreeContainsObjectRestOrSpread
SubtreeExclusionsBindingPattern = SubtreeExclusionsNode | SubtreeContainsRestOrSpread
// Masks
// - Additional bitmasks
SubtreeContainsLexicalThisOrSuper = SubtreeContainsLexicalThis | SubtreeContainsLexicalSuper
)
func propagateEraseableSyntaxListSubtreeFacts(children *TypeArgumentList) SubtreeFacts {
return core.IfElse(children != nil, SubtreeContainsTypeScript, SubtreeFactsNone)
}
func propagateEraseableSyntaxSubtreeFacts(child *TypeNode) SubtreeFacts {
return core.IfElse(child != nil, SubtreeContainsTypeScript, SubtreeFactsNone)
}
func propagateObjectBindingElementSubtreeFacts(child *BindingElementNode) SubtreeFacts {
facts := propagateSubtreeFacts(child)
if facts&SubtreeContainsRestOrSpread != 0 {
facts &= ^SubtreeContainsRestOrSpread
facts |= SubtreeContainsObjectRestOrSpread | SubtreeContainsESObjectRestOrSpread
}
return facts
}
func propagateBindingElementSubtreeFacts(child *BindingElementNode) SubtreeFacts {
return propagateSubtreeFacts(child) & ^SubtreeContainsRestOrSpread
}
func propagateSubtreeFacts(child *Node) SubtreeFacts {
if child == nil {
return SubtreeFactsNone
}
return child.propagateSubtreeFacts()
}
func propagateNodeListSubtreeFacts(children *NodeList, propagate func(*Node) SubtreeFacts) SubtreeFacts {
if children == nil {
return SubtreeFactsNone
}
facts := SubtreeFactsNone
for _, child := range children.Nodes {
facts |= propagate(child)
}
return facts
}
func propagateModifierListSubtreeFacts(children *ModifierList) SubtreeFacts {
if children == nil {
return SubtreeFactsNone
}
return propagateNodeListSubtreeFacts(&children.NodeList, propagateSubtreeFacts)
}

View File

@ -0,0 +1,59 @@
package ast
import (
"sync/atomic"
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/collections"
)
// Symbol
type Symbol struct {
Flags SymbolFlags
CheckFlags CheckFlags // Non-zero only in transient symbols created by Checker
Name string
Declarations []*Node
ValueDeclaration *Node
Members SymbolTable
Exports SymbolTable
id atomic.Uint64
Parent *Symbol
ExportSymbol *Symbol
AssignmentDeclarationMembers collections.Set[*Node] // Set of detected assignment declarations
GlobalExports SymbolTable // Conditional global UMD exports
}
// SymbolTable
type SymbolTable map[string]*Symbol
const InternalSymbolNamePrefix = "\xFE" // Invalid UTF8 sequence, will never occur as IdentifierName
const (
InternalSymbolNameCall = InternalSymbolNamePrefix + "call" // Call signatures
InternalSymbolNameConstructor = InternalSymbolNamePrefix + "constructor" // Constructor implementations
InternalSymbolNameNew = InternalSymbolNamePrefix + "new" // Constructor signatures
InternalSymbolNameIndex = InternalSymbolNamePrefix + "index" // Index signatures
InternalSymbolNameExportStar = InternalSymbolNamePrefix + "export" // Module export * declarations
InternalSymbolNameGlobal = InternalSymbolNamePrefix + "global" // Global self-reference
InternalSymbolNameMissing = InternalSymbolNamePrefix + "missing" // Indicates missing symbol
InternalSymbolNameType = InternalSymbolNamePrefix + "type" // Anonymous type literal symbol
InternalSymbolNameObject = InternalSymbolNamePrefix + "object" // Anonymous object literal declaration
InternalSymbolNameJSXAttributes = InternalSymbolNamePrefix + "jsxAttributes" // Anonymous JSX attributes object literal declaration
InternalSymbolNameClass = InternalSymbolNamePrefix + "class" // Unnamed class expression
InternalSymbolNameFunction = InternalSymbolNamePrefix + "function" // Unnamed function expression
InternalSymbolNameComputed = InternalSymbolNamePrefix + "computed" // Computed property name declaration with dynamic name
InternalSymbolNameInstantiationExpression = InternalSymbolNamePrefix + "instantiationExpression" // Instantiation expressions
InternalSymbolNameImportAttributes = InternalSymbolNamePrefix + "importAttributes"
InternalSymbolNameExportEquals = "export=" // Export assignment symbol
InternalSymbolNameDefault = "default" // Default export symbol (technically not wholly internal, but included here for usability)
InternalSymbolNameThis = "this"
InternalSymbolNameModuleExports = "module.exports"
)
func SymbolName(symbol *Symbol) string {
if symbol.ValueDeclaration != nil && IsPrivateIdentifierClassElementDeclaration(symbol.ValueDeclaration) {
return symbol.ValueDeclaration.Name().Text()
}
return symbol.Name
}

View File

@ -0,0 +1,86 @@
package ast
// SymbolFlags
type SymbolFlags uint32
const (
SymbolFlagsNone SymbolFlags = 0
SymbolFlagsFunctionScopedVariable SymbolFlags = 1 << 0 // Variable (var) or parameter
SymbolFlagsBlockScopedVariable SymbolFlags = 1 << 1 // A block-scoped variable (let or const)
SymbolFlagsProperty SymbolFlags = 1 << 2 // Property or enum member
SymbolFlagsEnumMember SymbolFlags = 1 << 3 // Enum member
SymbolFlagsFunction SymbolFlags = 1 << 4 // Function
SymbolFlagsClass SymbolFlags = 1 << 5 // Class
SymbolFlagsInterface SymbolFlags = 1 << 6 // Interface
SymbolFlagsConstEnum SymbolFlags = 1 << 7 // Const enum
SymbolFlagsRegularEnum SymbolFlags = 1 << 8 // Enum
SymbolFlagsValueModule SymbolFlags = 1 << 9 // Instantiated module
SymbolFlagsNamespaceModule SymbolFlags = 1 << 10 // Uninstantiated module
SymbolFlagsTypeLiteral SymbolFlags = 1 << 11 // Type Literal or mapped type
SymbolFlagsObjectLiteral SymbolFlags = 1 << 12 // Object Literal
SymbolFlagsMethod SymbolFlags = 1 << 13 // Method
SymbolFlagsConstructor SymbolFlags = 1 << 14 // Constructor
SymbolFlagsGetAccessor SymbolFlags = 1 << 15 // Get accessor
SymbolFlagsSetAccessor SymbolFlags = 1 << 16 // Set accessor
SymbolFlagsSignature SymbolFlags = 1 << 17 // Call, construct, or index signature
SymbolFlagsTypeParameter SymbolFlags = 1 << 18 // Type parameter
SymbolFlagsTypeAlias SymbolFlags = 1 << 19 // Type alias
SymbolFlagsExportValue SymbolFlags = 1 << 20 // Exported value marker (see comment in declareModuleMember in binder)
SymbolFlagsAlias SymbolFlags = 1 << 21 // An alias for another symbol (see comment in isAliasSymbolDeclaration in checker)
SymbolFlagsPrototype SymbolFlags = 1 << 22 // Prototype property (no source representation)
SymbolFlagsExportStar SymbolFlags = 1 << 23 // Export * declaration
SymbolFlagsOptional SymbolFlags = 1 << 24 // Optional property
SymbolFlagsTransient SymbolFlags = 1 << 25 // Transient symbol (created during type check)
SymbolFlagsAssignment SymbolFlags = 1 << 26 // Assignment to property on function acting as declaration (eg `func.prop = 1`)
SymbolFlagsModuleExports SymbolFlags = 1 << 27 // Symbol for CommonJS `module` of `module.exports`
SymbolFlagsConstEnumOnlyModule SymbolFlags = 1 << 28 // Module contains only const enums or other modules with only const enums
SymbolFlagsReplaceableByMethod SymbolFlags = 1 << 29
SymbolFlagsGlobalLookup SymbolFlags = 1 << 30 // Flag to signal this is a global lookup
SymbolFlagsAll SymbolFlags = 1<<30 - 1 // All flags except SymbolFlagsGlobalLookup
SymbolFlagsEnum = SymbolFlagsRegularEnum | SymbolFlagsConstEnum
SymbolFlagsVariable = SymbolFlagsFunctionScopedVariable | SymbolFlagsBlockScopedVariable
SymbolFlagsValue = SymbolFlagsVariable | SymbolFlagsProperty | SymbolFlagsEnumMember | SymbolFlagsObjectLiteral | SymbolFlagsFunction | SymbolFlagsClass | SymbolFlagsEnum | SymbolFlagsValueModule | SymbolFlagsMethod | SymbolFlagsGetAccessor | SymbolFlagsSetAccessor
SymbolFlagsType = SymbolFlagsClass | SymbolFlagsInterface | SymbolFlagsEnum | SymbolFlagsEnumMember | SymbolFlagsTypeLiteral | SymbolFlagsTypeParameter | SymbolFlagsTypeAlias
SymbolFlagsNamespace = SymbolFlagsValueModule | SymbolFlagsNamespaceModule | SymbolFlagsEnum
SymbolFlagsModule = SymbolFlagsValueModule | SymbolFlagsNamespaceModule
SymbolFlagsAccessor = SymbolFlagsGetAccessor | SymbolFlagsSetAccessor
// Variables can be redeclared, but can not redeclare a block-scoped declaration with the
// same name, or any other value that is not a variable, e.g. ValueModule or Class
SymbolFlagsFunctionScopedVariableExcludes = SymbolFlagsValue & ^SymbolFlagsFunctionScopedVariable
// Block-scoped declarations are not allowed to be re-declared
// they can not merge with anything in the value space
SymbolFlagsBlockScopedVariableExcludes = SymbolFlagsValue
SymbolFlagsParameterExcludes = SymbolFlagsValue
SymbolFlagsPropertyExcludes = SymbolFlagsValue & ^SymbolFlagsProperty
SymbolFlagsEnumMemberExcludes = SymbolFlagsValue | SymbolFlagsType
SymbolFlagsFunctionExcludes = SymbolFlagsValue & ^(SymbolFlagsFunction | SymbolFlagsValueModule | SymbolFlagsClass)
SymbolFlagsClassExcludes = (SymbolFlagsValue | SymbolFlagsType) & ^(SymbolFlagsValueModule | SymbolFlagsInterface | SymbolFlagsFunction) // class-interface mergability done in checker.ts
SymbolFlagsInterfaceExcludes = SymbolFlagsType & ^(SymbolFlagsInterface | SymbolFlagsClass)
SymbolFlagsRegularEnumExcludes = (SymbolFlagsValue | SymbolFlagsType) & ^(SymbolFlagsRegularEnum | SymbolFlagsValueModule) // regular enums merge only with regular enums and modules
SymbolFlagsConstEnumExcludes = (SymbolFlagsValue | SymbolFlagsType) & ^SymbolFlagsConstEnum // const enums merge only with const enums
SymbolFlagsValueModuleExcludes = SymbolFlagsValue & ^(SymbolFlagsFunction | SymbolFlagsClass | SymbolFlagsRegularEnum | SymbolFlagsValueModule)
SymbolFlagsNamespaceModuleExcludes = SymbolFlagsNone
SymbolFlagsMethodExcludes = SymbolFlagsValue & ^SymbolFlagsMethod
SymbolFlagsGetAccessorExcludes = SymbolFlagsValue & ^SymbolFlagsSetAccessor
SymbolFlagsSetAccessorExcludes = SymbolFlagsValue & ^SymbolFlagsGetAccessor
SymbolFlagsAccessorExcludes = SymbolFlagsValue
SymbolFlagsTypeParameterExcludes = SymbolFlagsType & ^SymbolFlagsTypeParameter
SymbolFlagsTypeAliasExcludes = SymbolFlagsType
SymbolFlagsAliasExcludes = SymbolFlagsAlias
SymbolFlagsModuleMember = SymbolFlagsVariable | SymbolFlagsFunction | SymbolFlagsClass | SymbolFlagsInterface | SymbolFlagsEnum | SymbolFlagsModule | SymbolFlagsTypeAlias | SymbolFlagsAlias
SymbolFlagsExportHasLocal = SymbolFlagsFunction | SymbolFlagsClass | SymbolFlagsEnum | SymbolFlagsValueModule
SymbolFlagsBlockScoped = SymbolFlagsBlockScopedVariable | SymbolFlagsClass | SymbolFlagsEnum
SymbolFlagsPropertyOrAccessor = SymbolFlagsProperty | SymbolFlagsAccessor
SymbolFlagsClassMember = SymbolFlagsMethod | SymbolFlagsAccessor | SymbolFlagsProperty
SymbolFlagsExportSupportsDefaultModifier = SymbolFlagsClass | SymbolFlagsFunction | SymbolFlagsInterface
SymbolFlagsExportDoesNotSupportDefaultModifier = ^SymbolFlagsExportSupportsDefaultModifier
// The set of things we consider semantically classifiable. Used to speed up the LS during
// classification.
SymbolFlagsClassifiable = SymbolFlagsClass | SymbolFlagsEnum | SymbolFlagsTypeAlias | SymbolFlagsInterface | SymbolFlagsTypeParameter | SymbolFlagsModule | SymbolFlagsAlias
SymbolFlagsLateBindingContainer = SymbolFlagsClass | SymbolFlagsInterface | SymbolFlagsTypeLiteral | SymbolFlagsObjectLiteral | SymbolFlagsFunction
)

View File

@ -0,0 +1,31 @@
package ast
type TokenFlags int32
const (
TokenFlagsNone TokenFlags = 0
TokenFlagsPrecedingLineBreak TokenFlags = 1 << 0
TokenFlagsPrecedingJSDocComment TokenFlags = 1 << 1
TokenFlagsUnterminated TokenFlags = 1 << 2
TokenFlagsExtendedUnicodeEscape TokenFlags = 1 << 3 // e.g. `\u{10ffff}`
TokenFlagsScientific TokenFlags = 1 << 4 // e.g. `10e2`
TokenFlagsOctal TokenFlags = 1 << 5 // e.g. `0777`
TokenFlagsHexSpecifier TokenFlags = 1 << 6 // e.g. `0x00000000`
TokenFlagsBinarySpecifier TokenFlags = 1 << 7 // e.g. `0b0110010000000000`
TokenFlagsOctalSpecifier TokenFlags = 1 << 8 // e.g. `0o777`
TokenFlagsContainsSeparator TokenFlags = 1 << 9 // e.g. `0b1100_0101`
TokenFlagsUnicodeEscape TokenFlags = 1 << 10 // e.g. `\u00a0`
TokenFlagsContainsInvalidEscape TokenFlags = 1 << 11 // e.g. `\uhello`
TokenFlagsHexEscape TokenFlags = 1 << 12 // e.g. `\xa0`
TokenFlagsContainsLeadingZero TokenFlags = 1 << 13 // e.g. `0888`
TokenFlagsContainsInvalidSeparator TokenFlags = 1 << 14 // e.g. `0_1`
TokenFlagsPrecedingJSDocLeadingAsterisks TokenFlags = 1 << 15
TokenFlagsSingleQuote TokenFlags = 1 << 16 // e.g. `'abc'`
TokenFlagsBinaryOrOctalSpecifier TokenFlags = TokenFlagsBinarySpecifier | TokenFlagsOctalSpecifier
TokenFlagsWithSpecifier TokenFlags = TokenFlagsHexSpecifier | TokenFlagsBinaryOrOctalSpecifier
TokenFlagsStringLiteralFlags TokenFlags = TokenFlagsUnterminated | TokenFlagsHexEscape | TokenFlagsUnicodeEscape | TokenFlagsExtendedUnicodeEscape | TokenFlagsContainsInvalidEscape | TokenFlagsSingleQuote
TokenFlagsNumericLiteralFlags TokenFlags = TokenFlagsScientific | TokenFlagsOctal | TokenFlagsContainsLeadingZero | TokenFlagsWithSpecifier | TokenFlagsContainsSeparator | TokenFlagsContainsInvalidSeparator
TokenFlagsTemplateLiteralLikeFlags TokenFlags = TokenFlagsUnterminated | TokenFlagsHexEscape | TokenFlagsUnicodeEscape | TokenFlagsExtendedUnicodeEscape | TokenFlagsContainsInvalidEscape
TokenFlagsRegularExpressionLiteralFlags TokenFlags = TokenFlagsUnterminated
TokenFlagsIsInvalid TokenFlags = TokenFlagsOctal | TokenFlagsContainsLeadingZero | TokenFlagsContainsInvalidSeparator | TokenFlagsContainsInvalidEscape
)

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,278 @@
package ast
import (
"slices"
)
// NodeVisitor
type NodeVisitor struct {
Visit func(node *Node) *Node // Required. The callback used to visit a node
Factory *NodeFactory // Required. The NodeFactory used to produce new nodes when passed to VisitEachChild
Hooks NodeVisitorHooks // Hooks to be invoked when visiting a node
}
// These hooks are used to intercept the default behavior of the visitor
type NodeVisitorHooks struct {
VisitNode func(node *Node, v *NodeVisitor) *Node // Overrides visiting a Node. Only invoked by the VisitEachChild method on a given Node subtype.
VisitToken func(node *TokenNode, v *NodeVisitor) *Node // Overrides visiting a TokenNode. Only invoked by the VisitEachChild method on a given Node subtype.
VisitNodes func(nodes *NodeList, v *NodeVisitor) *NodeList // Overrides visiting a NodeList. Only invoked by the VisitEachChild method on a given Node subtype.
VisitModifiers func(nodes *ModifierList, v *NodeVisitor) *ModifierList // Overrides visiting a ModifierList. Only invoked by the VisitEachChild method on a given Node subtype.
VisitEmbeddedStatement func(node *Statement, v *NodeVisitor) *Statement // Overrides visiting a Node when it is the embedded statement body of an iteration statement, `if` statement, or `with` statement. Only invoked by the VisitEachChild method on a given Node subtype.
VisitIterationBody func(node *Statement, v *NodeVisitor) *Statement // Overrides visiting a Node when it is the embedded statement body of an iteration statement. Only invoked by the VisitEachChild method on a given Node subtype.
VisitParameters func(nodes *ParameterList, v *NodeVisitor) *ParameterList // Overrides visiting a ParameterList. Only invoked by the VisitEachChild method on a given Node subtype.
VisitFunctionBody func(node *BlockOrExpression, v *NodeVisitor) *BlockOrExpression // Overrides visiting a function body. Only invoked by the VisitEachChild method on a given Node subtype.
VisitTopLevelStatements func(nodes *StatementList, v *NodeVisitor) *StatementList // Overrides visiting a variable environment. Only invoked by the VisitEachChild method on a given Node subtype.
}
func NewNodeVisitor(visit func(node *Node) *Node, factory *NodeFactory, hooks NodeVisitorHooks) *NodeVisitor {
if factory == nil {
factory = &NodeFactory{}
}
return &NodeVisitor{Visit: visit, Factory: factory, Hooks: hooks}
}
func (v *NodeVisitor) VisitSourceFile(node *SourceFile) *SourceFile {
return v.VisitNode(node.AsNode()).AsSourceFile()
}
// Visits a Node, possibly returning a new Node in its place.
//
// - If the input node is nil, then the output is nil.
// - If v.Visit is nil, then the output is the input.
// - If v.Visit returns nil, then the output is nil.
// - If v.Visit returns a SyntaxList Node, then the output is the only child of the SyntaxList Node.
func (v *NodeVisitor) VisitNode(node *Node) *Node {
if node == nil || v.Visit == nil {
return node
}
if v.Visit != nil {
visited := v.Visit(node)
if visited != nil && visited.Kind == KindSyntaxList {
nodes := visited.AsSyntaxList().Children
if len(nodes) != 1 {
panic("Expected only a single node to be written to output")
}
visited = nodes[0]
if visited != nil && visited.Kind == KindSyntaxList {
panic("The result of visiting and lifting a Node may not be SyntaxList")
}
}
return visited
}
return node
}
// Visits an embedded Statement (i.e., the single statement body of a loop, `if..else` branch, etc.), possibly returning a new Statement in its place.
//
// - If the input node is nil, then the output is nil.
// - If v.Visit is nil, then the output is the input.
// - If v.Visit returns nil, then the output is nil.
// - If v.Visit returns a SyntaxList Node, then the output is either the only child of the SyntaxList Node, or a Block containing the nodes in the list.
func (v *NodeVisitor) VisitEmbeddedStatement(node *Statement) *Statement {
if node == nil || v.Visit == nil {
return node
}
if v.Visit != nil {
return v.liftToBlock(v.Visit(node))
}
return node
}
// Visits a NodeList, possibly returning a new NodeList in its place.
//
// - If the input NodeList is nil, the output is nil.
// - If v.Visit is nil, then the output is the input.
// - If v.Visit returns nil, the visited Node will be absent in the output.
// - If v.Visit returns a different Node than the input, a new NodeList will be generated and returned.
// - If v.Visit returns a SyntaxList Node, then the children of that node will be merged into the output and a new NodeList will be returned.
// - If this method returns a new NodeList for any reason, it will have the same Loc as the input NodeList.
func (v *NodeVisitor) VisitNodes(nodes *NodeList) *NodeList {
if nodes == nil || v.Visit == nil {
return nodes
}
if result, changed := v.VisitSlice(nodes.Nodes); changed {
list := v.Factory.NewNodeList(result)
list.Loc = nodes.Loc
return list
}
return nodes
}
// Visits a ModifierList, possibly returning a new ModifierList in its place.
//
// - If the input ModifierList is nil, the output is nil.
// - If v.Visit is nil, then the output is the input.
// - If v.Visit returns nil, the visited Node will be absent in the output.
// - If v.Visit returns a different Node than the input, a new ModifierList will be generated and returned.
// - If v.Visit returns a SyntaxList Node, then the children of that node will be merged into the output and a new NodeList will be returned.
// - If this method returns a new NodeList for any reason, it will have the same Loc as the input NodeList.
func (v *NodeVisitor) VisitModifiers(nodes *ModifierList) *ModifierList {
if nodes == nil || v.Visit == nil {
return nodes
}
if result, changed := v.VisitSlice(nodes.Nodes); changed {
list := v.Factory.NewModifierList(result)
list.Loc = nodes.Loc
return list
}
return nodes
}
// Visits a slice of Nodes, returning the resulting slice and a value indicating whether the slice was changed.
//
// - If the input slice is nil, the output is nil.
// - If v.Visit is nil, then the output is the input.
// - If v.Visit returns nil, the visited Node will be absent in the output.
// - If v.Visit returns a different Node than the input, a new slice will be generated and returned.
// - If v.Visit returns a SyntaxList Node, then the children of that node will be merged into the output and a new slice will be returned.
func (v *NodeVisitor) VisitSlice(nodes []*Node) (result []*Node, changed bool) {
if nodes == nil || v.Visit == nil {
return nodes, false
}
for i := 0; i < len(nodes); i++ {
node := nodes[i]
if v.Visit == nil {
break
}
visited := v.Visit(node)
if visited == nil || visited != node {
updated := slices.Clone(nodes[:i])
for {
// finish prior loop
switch {
case visited == nil: // do nothing
case visited.Kind == KindSyntaxList:
updated = append(updated, visited.AsSyntaxList().Children...)
default:
updated = append(updated, visited)
}
i++
// loop over remaining elements
if i >= len(nodes) {
break
}
if v.Visit != nil {
node = nodes[i]
visited = v.Visit(node)
} else {
updated = append(updated, nodes[i:]...)
break
}
}
return updated, true
}
}
return nodes, false
}
// Visits each child of a Node, possibly returning a new Node of the same kind in its place.
func (v *NodeVisitor) VisitEachChild(node *Node) *Node {
if node == nil || v.Visit == nil {
return node
}
return node.VisitEachChild(v)
}
func (v *NodeVisitor) visitNode(node *Node) *Node {
if v.Hooks.VisitNode != nil {
return v.Hooks.VisitNode(node, v)
}
return v.VisitNode(node)
}
func (v *NodeVisitor) visitEmbeddedStatement(node *Node) *Node {
if v.Hooks.VisitEmbeddedStatement != nil {
return v.Hooks.VisitEmbeddedStatement(node, v)
}
if v.Hooks.VisitNode != nil {
return v.liftToBlock(v.Hooks.VisitNode(node, v))
}
return v.VisitEmbeddedStatement(node)
}
func (v *NodeVisitor) visitIterationBody(node *Statement) *Statement {
if v.Hooks.VisitIterationBody != nil {
return v.Hooks.VisitIterationBody(node, v)
}
return v.visitEmbeddedStatement(node)
}
func (v *NodeVisitor) visitFunctionBody(node *BlockOrExpression) *BlockOrExpression {
if v.Hooks.VisitFunctionBody != nil {
return v.Hooks.VisitFunctionBody(node, v)
}
return v.visitNode(node)
}
func (v *NodeVisitor) visitToken(node *Node) *Node {
if v.Hooks.VisitToken != nil {
return v.Hooks.VisitToken(node, v)
}
return v.VisitNode(node)
}
func (v *NodeVisitor) visitNodes(nodes *NodeList) *NodeList {
if v.Hooks.VisitNodes != nil {
return v.Hooks.VisitNodes(nodes, v)
}
return v.VisitNodes(nodes)
}
func (v *NodeVisitor) visitModifiers(nodes *ModifierList) *ModifierList {
if v.Hooks.VisitModifiers != nil {
return v.Hooks.VisitModifiers(nodes, v)
}
return v.VisitModifiers(nodes)
}
func (v *NodeVisitor) visitParameters(nodes *ParameterList) *ParameterList {
if v.Hooks.VisitParameters != nil {
return v.Hooks.VisitParameters(nodes, v)
}
return v.visitNodes(nodes)
}
func (v *NodeVisitor) visitTopLevelStatements(nodes *StatementList) *StatementList {
if v.Hooks.VisitTopLevelStatements != nil {
return v.Hooks.VisitTopLevelStatements(nodes, v)
}
return v.visitNodes(nodes)
}
func (v *NodeVisitor) liftToBlock(node *Statement) *Statement {
var nodes []*Node
if node != nil {
if node.Kind == KindSyntaxList {
nodes = node.AsSyntaxList().Children
} else {
nodes = []*Node{node}
}
}
if len(nodes) == 1 {
node = nodes[0]
} else {
node = v.Factory.NewBlock(v.Factory.NewNodeList(nodes), true /*multiLine*/)
}
if node.Kind == KindSyntaxList {
panic("The result of visiting and lifting a Node may not be SyntaxList")
}
return node
}

View File

@ -0,0 +1,69 @@
package collections
import (
"iter"
"maps"
"slices"
)
type MultiMap[K comparable, V comparable] struct {
M map[K][]V
}
func GroupBy[K comparable, V comparable](items []V, groupId func(V) K) *MultiMap[K, V] {
m := &MultiMap[K, V]{}
for _, item := range items {
m.Add(groupId(item), item)
}
return m
}
func (s *MultiMap[K, V]) Has(key K) bool {
_, ok := s.M[key]
return ok
}
func (s *MultiMap[K, V]) Get(key K) []V {
return s.M[key]
}
func (s *MultiMap[K, V]) Add(key K, value V) {
if s.M == nil {
s.M = make(map[K][]V)
}
s.M[key] = append(s.M[key], value)
}
func (s *MultiMap[K, V]) Remove(key K, value V) {
if values, ok := s.M[key]; ok {
i := slices.Index(values, value)
if i >= 0 {
if len(values) == 1 {
delete(s.M, key)
} else {
values = append(values[:i], values[i+1:]...)
s.M[key] = values
}
}
}
}
func (s *MultiMap[K, V]) RemoveAll(key K) {
delete(s.M, key)
}
func (s *MultiMap[K, V]) Len() int {
return len(s.M)
}
func (s *MultiMap[K, V]) Keys() iter.Seq[K] {
return maps.Keys(s.M)
}
func (s *MultiMap[K, V]) Values() iter.Seq[[]V] {
return maps.Values(s.M)
}
func (s *MultiMap[K, V]) Clear() {
clear(s.M)
}

View File

@ -0,0 +1,317 @@
package collections
import (
"encoding"
"errors"
"iter"
"maps"
"reflect"
"slices"
"strconv"
"github.com/go-json-experiment/json"
"github.com/go-json-experiment/json/jsontext"
)
// OrderedMap is an insertion ordered map.
type OrderedMap[K comparable, V any] struct {
_ noCopy
keys []K
mp map[K]V
}
// noCopy may be embedded into structs which must not be copied
// after the first use.
//
// See https://golang.org/issues/8005#issuecomment-190753527
// for details.
type noCopy struct{}
// Lock is a no-op used by -copylocks checker from `go vet`.
func (*noCopy) Lock() {}
func (*noCopy) Unlock() {}
// NewOrderedMapWithSizeHint creates a new OrderedMap with a hint for the number of elements it will contain.
func NewOrderedMapWithSizeHint[K comparable, V any](hint int) *OrderedMap[K, V] {
m := newMapWithSizeHint[K, V](hint)
return &m
}
func newMapWithSizeHint[K comparable, V any](hint int) OrderedMap[K, V] {
return OrderedMap[K, V]{
keys: make([]K, 0, hint),
mp: make(map[K]V, hint),
}
}
type MapEntry[K comparable, V any] struct {
Key K
Value V
}
func NewOrderedMapFromList[K comparable, V any](items []MapEntry[K, V]) *OrderedMap[K, V] {
mp := NewOrderedMapWithSizeHint[K, V](len(items))
for _, item := range items {
mp.Set(item.Key, item.Value)
}
return mp
}
// Set sets a key-value pair in the map.
func (m *OrderedMap[K, V]) Set(key K, value V) {
if m.mp == nil {
m.mp = make(map[K]V)
}
if _, ok := m.mp[key]; !ok {
m.keys = append(m.keys, key)
}
m.mp[key] = value
}
// Get retrieves a value from the map.
func (m *OrderedMap[K, V]) Get(key K) (V, bool) {
v, ok := m.mp[key]
return v, ok
}
// GetOrZero retrieves a value from the map, or returns the zero value of the value type if the key is not present.
func (m *OrderedMap[K, V]) GetOrZero(key K) V {
return m.mp[key]
}
// EntryAt retrieves the key-value pair at the specified index.
func (m *OrderedMap[K, V]) EntryAt(index int) (K, V, bool) {
if index < 0 || index >= len(m.keys) {
var zero K
var zeroV V
return zero, zeroV, false
}
key := m.keys[index]
value := m.mp[key]
return key, value, true
}
// Has returns true if the map contains the key.
func (m *OrderedMap[K, V]) Has(key K) bool {
_, ok := m.mp[key]
return ok
}
// Delete removes a key-value pair from the map.
func (m *OrderedMap[K, V]) Delete(key K) (V, bool) {
v, ok := m.mp[key]
if !ok {
var zero V
return zero, false
}
delete(m.mp, key)
i := slices.Index(m.keys, key)
// If we're just removing the first or last element, avoid shifting everything around.
if i == 0 {
var zero K
m.keys[0] = zero
m.keys = m.keys[1:]
} else if end := len(m.keys) - 1; i == end {
var zero K
m.keys[end] = zero
m.keys = m.keys[:end]
} else {
m.keys = slices.Delete(m.keys, i, i+1)
}
return v, true
}
// Keys returns an iterator over the keys in the map.
// A slice of the keys can be obtained by calling `slices.Collect`.
func (m *OrderedMap[K, V]) Keys() iter.Seq[K] {
return func(yield func(K) bool) {
if m == nil {
return
}
// We use a for loop here to ensure we enumerate new items added during iteration.
//nolint:intrange
for i := 0; i < len(m.keys); i++ {
if !yield(m.keys[i]) {
break
}
}
}
}
// Values returns an iterator over the values in the map.
// A slice of the values can be obtained by calling `slices.Collect`.
func (m *OrderedMap[K, V]) Values() iter.Seq[V] {
return func(yield func(V) bool) {
if m == nil {
return
}
// We use a for loop here to ensure we enumerate new items added during iteration.
//nolint:intrange
for i := 0; i < len(m.keys); i++ {
if !yield(m.mp[m.keys[i]]) {
break
}
}
}
}
// Entries returns an iterator over the key-value pairs in the map.
func (m *OrderedMap[K, V]) Entries() iter.Seq2[K, V] {
return func(yield func(K, V) bool) {
if m == nil {
return
}
// We use a for loop here to ensure we enumerate new items added during iteration.
//nolint:intrange
for i := 0; i < len(m.keys); i++ {
key := m.keys[i]
if !yield(key, m.mp[key]) {
break
}
}
}
}
// Clear removes all key-value pairs from the map.
// The space allocated for the map will be reused.
func (m *OrderedMap[K, V]) Clear() {
clear(m.keys)
m.keys = m.keys[:0]
clear(m.mp)
}
// Size returns the number of key-value pairs in the map.
func (m *OrderedMap[K, V]) Size() int {
if m == nil {
return 0
}
return len(m.keys)
}
// Clone returns a shallow copy of the map.
func (m *OrderedMap[K, V]) Clone() *OrderedMap[K, V] {
if m == nil {
return nil
}
m2 := m.clone()
return &m2
}
func (m *OrderedMap[K, V]) clone() OrderedMap[K, V] {
return OrderedMap[K, V]{
keys: slices.Clone(m.keys),
mp: maps.Clone(m.mp),
}
}
var _ json.MarshalerTo = (*OrderedMap[string, string])(nil)
func (m *OrderedMap[K, V]) MarshalJSONTo(enc *jsontext.Encoder) error {
if err := enc.WriteToken(jsontext.BeginObject); err != nil {
return err
}
for _, k := range m.keys {
// TODO: is this needed? Can we just MarshalEncode k directly?
keyString, err := resolveKeyName(reflect.ValueOf(k))
if err != nil {
return err
}
if err := json.MarshalEncode(enc, keyString); err != nil {
return err
}
if err := json.MarshalEncode(enc, m.mp[k]); err != nil {
return err
}
}
return enc.WriteToken(jsontext.EndObject)
}
func resolveKeyName(k reflect.Value) (string, error) {
if k.Kind() == reflect.String {
return k.String(), nil
}
if tm, ok := reflect.TypeAssert[encoding.TextMarshaler](k); ok {
if k.Kind() == reflect.Pointer && k.IsNil() {
return "", nil
}
buf, err := tm.MarshalText()
return string(buf), err
}
switch k.Kind() {
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
return strconv.FormatInt(k.Int(), 10), nil
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
return strconv.FormatUint(k.Uint(), 10), nil
}
panic("unexpected map key type")
}
var _ json.UnmarshalerFrom = (*OrderedMap[string, string])(nil)
func (m *OrderedMap[K, V]) UnmarshalJSONFrom(dec *jsontext.Decoder) error {
token, err := dec.ReadToken()
if err != nil {
return err
}
if token.Kind() == 'n' { // jsontext.Null.Kind()
// By convention, to approximate the behavior of Unmarshal itself,
// Unmarshalers implement UnmarshalJSON([]byte("null")) as a no-op.
// https://pkg.go.dev/encoding/json#Unmarshaler
// TODO: reconsider
return nil
}
if token.Kind() != '{' { // jsontext.ObjectStart.Kind()
return errors.New("cannot unmarshal non-object JSON value into Map")
}
for dec.PeekKind() != '}' { // jsontext.ObjectEnd.Kind()
var key K
var value V
if err := json.UnmarshalDecode(dec, &key); err != nil {
return err
}
if err := json.UnmarshalDecode(dec, &value); err != nil {
return err
}
m.Set(key, value)
}
if _, err := dec.ReadToken(); err != nil {
return err
}
return nil
}
func DiffOrderedMaps[K comparable, V comparable](m1 *OrderedMap[K, V], m2 *OrderedMap[K, V], onAdded func(key K, value V), onRemoved func(key K, value V), onModified func(key K, oldValue V, newValue V)) {
DiffOrderedMapsFunc(m1, m2, func(a, b V) bool {
return a == b
}, onAdded, onRemoved, onModified)
}
func DiffOrderedMapsFunc[K comparable, V any](m1 *OrderedMap[K, V], m2 *OrderedMap[K, V], equalValues func(a, b V) bool, onAdded func(key K, value V), onRemoved func(key K, value V), onModified func(key K, oldValue V, newValue V)) {
for k, v2 := range m2.Entries() {
if _, ok := m1.Get(k); !ok {
onAdded(k, v2)
}
}
for k, v1 := range m1.Entries() {
if v2, ok := m2.Get(k); ok {
if !equalValues(v1, v2) {
onModified(k, v1, v2)
}
} else {
onRemoved(k, v1)
}
}
}

View File

@ -0,0 +1,54 @@
package collections
import "iter"
// OrderedSet an insertion ordered set.
type OrderedSet[T comparable] struct {
m OrderedMap[T, struct{}]
}
// NewOrderedSetWithSizeHint creates a new OrderedSet with a hint for the number of elements it will contain.
func NewOrderedSetWithSizeHint[T comparable](hint int) *OrderedSet[T] {
return &OrderedSet[T]{
m: newMapWithSizeHint[T, struct{}](hint),
}
}
// Add adds a value to the set.
func (s *OrderedSet[T]) Add(value T) {
s.m.Set(value, struct{}{})
}
// Has returns true if the set contains the value.
func (s *OrderedSet[T]) Has(value T) bool {
return s.m.Has(value)
}
// Delete removes a value from the set.
func (s *OrderedSet[T]) Delete(value T) bool {
_, ok := s.m.Delete(value)
return ok
}
// Values returns an iterator over the values in the set.
func (s *OrderedSet[T]) Values() iter.Seq[T] {
return s.m.Keys()
}
// Clear removes all elements from the set.
// The space allocated for the set will be reused.
func (s *OrderedSet[T]) Clear() {
s.m.Clear()
}
// Size returns the number of elements in the set.
func (s *OrderedSet[T]) Size() int {
return s.m.Size()
}
// Clone returns a shallow copy of the set.
func (s *OrderedSet[T]) Clone() *OrderedSet[T] {
return &OrderedSet[T]{
m: s.m.clone(),
}
}

View File

@ -0,0 +1,77 @@
package collections
import "maps"
type Set[T comparable] struct {
M map[T]struct{}
}
// NewSetWithSizeHint creates a new Set with a hint for the number of elements it will contain.
func NewSetWithSizeHint[T comparable](hint int) *Set[T] {
return &Set[T]{
M: make(map[T]struct{}, hint),
}
}
func (s *Set[T]) Has(key T) bool {
_, ok := s.M[key]
return ok
}
func (s *Set[T]) Add(key T) {
if s.M == nil {
s.M = make(map[T]struct{})
}
s.M[key] = struct{}{}
}
func (s *Set[T]) Delete(key T) {
delete(s.M, key)
}
func (s *Set[T]) Len() int {
return len(s.M)
}
func (s *Set[T]) Keys() map[T]struct{} {
return s.M
}
func (s *Set[T]) Clear() {
clear(s.M)
}
// Returns true if the key was not already present in the set.
func (s *Set[T]) AddIfAbsent(key T) bool {
if s.Has(key) {
return false
}
s.Add(key)
return true
}
func (s *Set[T]) Clone() *Set[T] {
if s == nil {
return nil
}
clone := &Set[T]{M: maps.Clone(s.M)}
return clone
}
func (s *Set[T]) Equals(other *Set[T]) bool {
if s == other {
return true
}
if s == nil || other == nil {
return false
}
return maps.Equal(s.M, other.M)
}
func NewSetFromItems[T comparable](items ...T) *Set[T] {
s := &Set[T]{}
for _, item := range items {
s.Add(item)
}
return s
}

View File

@ -0,0 +1,84 @@
package collections
import (
"iter"
"sync"
)
type SyncMap[K comparable, V any] struct {
_ [0]K
_ [0]V
m sync.Map
}
func (s *SyncMap[K, V]) Load(key K) (value V, ok bool) {
val, ok := s.m.Load(key)
if !ok {
return value, ok
}
return val.(V), true
}
func (s *SyncMap[K, V]) Store(key K, value V) {
s.m.Store(key, value)
}
func (s *SyncMap[K, V]) LoadOrStore(key K, value V) (actual V, loaded bool) {
actualAny, loaded := s.m.LoadOrStore(key, value)
return actualAny.(V), loaded
}
func (s *SyncMap[K, V]) Delete(key K) {
s.m.Delete(key)
}
func (s *SyncMap[K, V]) Clear() {
s.m.Clear()
}
func (s *SyncMap[K, V]) Range(f func(key K, value V) bool) {
s.m.Range(func(key, value any) bool {
return f(key.(K), value.(V))
})
}
// Size returns the approximate number of items in the map.
// Note that this is not a precise count, as the map may be modified
// concurrently while this method is running.
func (s *SyncMap[K, V]) Size() int {
count := 0
s.m.Range(func(_, _ any) bool {
count++
return true
})
return count
}
func (s *SyncMap[K, V]) ToMap() map[K]V {
m := make(map[K]V, s.Size())
s.m.Range(func(key, value any) bool {
m[key.(K)] = value.(V)
return true
})
return m
}
func (s *SyncMap[K, V]) Keys() iter.Seq[K] {
return func(yield func(K) bool) {
s.m.Range(func(key, value any) bool {
if !yield(key.(K)) {
return false
}
return true
})
}
}
func (s *SyncMap[K, V]) Clone() *SyncMap[K, V] {
clone := &SyncMap[K, V]{}
s.m.Range(func(key, value any) bool {
clone.m.Store(key, value)
return true
})
return clone
}

View File

@ -0,0 +1,67 @@
package collections
import "iter"
type SyncSet[T comparable] struct {
m SyncMap[T, struct{}]
}
func (s *SyncSet[T]) Has(key T) bool {
_, ok := s.m.Load(key)
return ok
}
func (s *SyncSet[T]) Add(key T) {
s.AddIfAbsent(key)
}
// AddIfAbsent adds the key to the set if it is not already present
// using LoadOrStore. It returns true if the key was not already present
// (opposite of the return value of LoadOrStore).
func (s *SyncSet[T]) AddIfAbsent(key T) bool {
_, loaded := s.m.LoadOrStore(key, struct{}{})
return !loaded
}
func (s *SyncSet[T]) Delete(key T) {
s.m.Delete(key)
}
func (s *SyncSet[T]) Range(fn func(key T) bool) {
s.m.Range(func(key T, value struct{}) bool {
return fn(key)
})
}
// Size returns the approximate number of items in the map.
// Note that this is not a precise count, as the map may be modified
// concurrently while this method is running.
func (s *SyncSet[T]) Size() int {
count := 0
s.m.Range(func(_ T, _ struct{}) bool {
count++
return true
})
return count
}
func (s *SyncSet[T]) ToSlice() []T {
var arr []T
arr = make([]T, 0, s.m.Size())
s.m.Range(func(key T, value struct{}) bool {
arr = append(arr, key)
return true
})
return arr
}
func (s *SyncSet[T]) Keys() iter.Seq[T] {
return func(yield func(T) bool) {
s.m.Range(func(key T, value struct{}) bool {
if !yield(key) {
return false
}
return true
})
}
}

View File

@ -0,0 +1,206 @@
package core
import (
"math"
"sync"
"sync/atomic"
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/collections"
)
type BreadthFirstSearchResult[N any] struct {
Stopped bool
Path []N
}
type breadthFirstSearchJob[N any] struct {
node N
parent *breadthFirstSearchJob[N]
}
type BreadthFirstSearchLevel[K comparable, N any] struct {
jobs *collections.OrderedMap[K, *breadthFirstSearchJob[N]]
}
func (l *BreadthFirstSearchLevel[K, N]) Has(key K) bool {
return l.jobs.Has(key)
}
func (l *BreadthFirstSearchLevel[K, N]) Delete(key K) {
l.jobs.Delete(key)
}
func (l *BreadthFirstSearchLevel[K, N]) Range(f func(node N) bool) {
for job := range l.jobs.Values() {
if !f(job.node) {
return
}
}
}
type BreadthFirstSearchOptions[K comparable, N any] struct {
// Visited is a set of nodes that have already been visited.
// If nil, a new set will be created.
Visited *collections.SyncSet[K]
// PreprocessLevel is a function that, if provided, will be called
// before each level, giving the caller an opportunity to remove nodes.
PreprocessLevel func(*BreadthFirstSearchLevel[K, N])
}
// BreadthFirstSearchParallel performs a breadth-first search on a graph
// starting from the given node. It processes nodes in parallel and returns the path
// from the first node that satisfies the `visit` function back to the start node.
func BreadthFirstSearchParallel[N comparable](
start N,
neighbors func(N) []N,
visit func(node N) (isResult bool, stop bool),
) BreadthFirstSearchResult[N] {
return BreadthFirstSearchParallelEx(start, neighbors, visit, BreadthFirstSearchOptions[N, N]{}, Identity)
}
// BreadthFirstSearchParallelEx is an extension of BreadthFirstSearchParallel that allows
// the caller to pass a pre-seeded set of already-visited nodes and a preprocessing function
// that can be used to remove nodes from each level before parallel processing.
func BreadthFirstSearchParallelEx[K comparable, N any](
start N,
neighbors func(N) []N,
visit func(node N) (isResult bool, stop bool),
options BreadthFirstSearchOptions[K, N],
getKey func(N) K,
) BreadthFirstSearchResult[N] {
visited := options.Visited
if visited == nil {
visited = &collections.SyncSet[K]{}
}
type result struct {
stop bool
job *breadthFirstSearchJob[N]
next *collections.OrderedMap[K, *breadthFirstSearchJob[N]]
}
var fallback *breadthFirstSearchJob[N]
// processLevel processes each node at the current level in parallel.
// It produces either a list of jobs to be processed in the next level,
// or a result if the visit function returns true for any node.
processLevel := func(index int, jobs *collections.OrderedMap[K, *breadthFirstSearchJob[N]]) result {
var lowestFallback atomic.Int64
var lowestGoal atomic.Int64
var nextJobCount atomic.Int64
lowestGoal.Store(math.MaxInt64)
lowestFallback.Store(math.MaxInt64)
if options.PreprocessLevel != nil {
options.PreprocessLevel(&BreadthFirstSearchLevel[K, N]{jobs: jobs})
}
next := make([][]*breadthFirstSearchJob[N], jobs.Size())
var wg sync.WaitGroup
i := 0
for j := range jobs.Values() {
wg.Add(1)
go func(i int, j *breadthFirstSearchJob[N]) {
defer wg.Done()
if int64(i) >= lowestGoal.Load() {
return // Stop processing if we already found a lower result
}
// If we have already visited this node, skip it.
if !visited.AddIfAbsent(getKey(j.node)) {
// Note that if we are here, we already visited this node at a
// previous *level*, which means `visit` must have returned false,
// so we don't need to update our result indices. This holds true
// because we deduplicated jobs before queuing the level.
return
}
isResult, stop := visit(j.node)
if isResult {
// We found a result, so we will stop at this level, but an
// earlier job may still find a true result at a lower index.
if stop {
updateMin(&lowestGoal, int64(i))
return
}
if fallback == nil {
updateMin(&lowestFallback, int64(i))
}
}
if int64(i) >= lowestGoal.Load() {
// If `visit` is expensive, it's likely that by the time we get here,
// a different job has already found a lower index result, so we
// don't even need to collect the next jobs.
return
}
// Add the next level jobs
neighborNodes := neighbors(j.node)
if len(neighborNodes) > 0 {
nextJobCount.Add(int64(len(neighborNodes)))
next[i] = Map(neighborNodes, func(child N) *breadthFirstSearchJob[N] {
return &breadthFirstSearchJob[N]{node: child, parent: j}
})
}
}(i, j)
i++
}
wg.Wait()
if index := lowestGoal.Load(); index != math.MaxInt64 {
// If we found a result, return it immediately.
_, job, _ := jobs.EntryAt(int(index))
return result{stop: true, job: job}
}
if fallback == nil {
if index := lowestFallback.Load(); index != math.MaxInt64 {
_, fallback, _ = jobs.EntryAt(int(index))
}
}
nextJobs := collections.NewOrderedMapWithSizeHint[K, *breadthFirstSearchJob[N]](int(nextJobCount.Load()))
for _, jobs := range next {
for _, j := range jobs {
if !nextJobs.Has(getKey(j.node)) {
// Deduplicate synchronously to avoid messy locks and spawning
// unnecessary goroutines.
nextJobs.Set(getKey(j.node), j)
}
}
}
return result{next: nextJobs}
}
createPath := func(job *breadthFirstSearchJob[N]) []N {
var path []N
for job != nil {
path = append(path, job.node)
job = job.parent
}
return path
}
levelIndex := 0
level := collections.NewOrderedMapFromList([]collections.MapEntry[K, *breadthFirstSearchJob[N]]{
{Key: getKey(start), Value: &breadthFirstSearchJob[N]{node: start}},
})
for level.Size() > 0 {
result := processLevel(levelIndex, level)
if result.stop {
return BreadthFirstSearchResult[N]{Stopped: true, Path: createPath(result.job)}
} else if result.job != nil && fallback == nil {
fallback = result.job
}
level = result.next
levelIndex++
}
return BreadthFirstSearchResult[N]{Stopped: false, Path: createPath(fallback)}
}
// updateMin updates the atomic integer `a` to the candidate value if it is less than the current value.
func updateMin(a *atomic.Int64, candidate int64) bool {
for {
current := a.Load()
if current < candidate {
return false
}
if a.CompareAndSwap(current, candidate) {
return true
}
}
}

View File

@ -0,0 +1,26 @@
package core
// BinarySearchUniqueFunc works like [slices.BinarySearchFunc], but avoids extra
// invocations of the comparison function by assuming that only one element
// in the slice could match the target. Also, unlike [slices.BinarySearchFunc],
// the comparison function is passed the current index of the element being
// compared, instead of the target element.
func BinarySearchUniqueFunc[S ~[]E, E any](x S, cmp func(int, E) int) (int, bool) {
n := len(x)
if n == 0 {
return 0, false
}
low, high := 0, n-1
for low <= high {
middle := low + ((high - low) >> 1)
value := cmp(middle, x[middle])
if value < 0 {
low = middle + 1
} else if value > 0 {
high = middle - 1
} else {
return middle, true
}
}
return low, false
}

View File

@ -0,0 +1,15 @@
package core
type BuildOptions struct {
_ noCopy
Dry Tristate `json:"dry,omitzero"`
Force Tristate `json:"force,omitzero"`
Verbose Tristate `json:"verbose,omitzero"`
StopBuildOnErrors Tristate `json:"stopBuildOnErrors,omitzero"`
// CompilerOptions are not parsed here and will be available on ParsedBuildCommandLine
// Internal fields
Clean Tristate `json:"clean,omitzero"`
}

View File

@ -0,0 +1,545 @@
package core
import (
"reflect"
"strings"
"sync"
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/collections"
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/tspath"
)
//go:generate go tool golang.org/x/tools/cmd/stringer -type=ModuleKind -trimprefix=ModuleKind -output=modulekind_stringer_generated.go
//go:generate go tool golang.org/x/tools/cmd/stringer -type=ScriptTarget -trimprefix=ScriptTarget -output=scripttarget_stringer_generated.go
//go:generate go tool mvdan.cc/gofumpt -w modulekind_stringer_generated.go scripttarget_stringer_generated.go
type CompilerOptions struct {
_ noCopy
AllowJs Tristate `json:"allowJs,omitzero"`
AllowArbitraryExtensions Tristate `json:"allowArbitraryExtensions,omitzero"`
AllowSyntheticDefaultImports Tristate `json:"allowSyntheticDefaultImports,omitzero"`
AllowImportingTsExtensions Tristate `json:"allowImportingTsExtensions,omitzero"`
AllowNonTsExtensions Tristate `json:"allowNonTsExtensions,omitzero"`
AllowUmdGlobalAccess Tristate `json:"allowUmdGlobalAccess,omitzero"`
AllowUnreachableCode Tristate `json:"allowUnreachableCode,omitzero"`
AllowUnusedLabels Tristate `json:"allowUnusedLabels,omitzero"`
AssumeChangesOnlyAffectDirectDependencies Tristate `json:"assumeChangesOnlyAffectDirectDependencies,omitzero"`
AlwaysStrict Tristate `json:"alwaysStrict,omitzero"`
CheckJs Tristate `json:"checkJs,omitzero"`
CustomConditions []string `json:"customConditions,omitzero"`
Composite Tristate `json:"composite,omitzero"`
EmitDeclarationOnly Tristate `json:"emitDeclarationOnly,omitzero"`
EmitBOM Tristate `json:"emitBOM,omitzero"`
EmitDecoratorMetadata Tristate `json:"emitDecoratorMetadata,omitzero"`
DownlevelIteration Tristate `json:"downlevelIteration,omitzero"`
Declaration Tristate `json:"declaration,omitzero"`
DeclarationDir string `json:"declarationDir,omitzero"`
DeclarationMap Tristate `json:"declarationMap,omitzero"`
DisableSizeLimit Tristate `json:"disableSizeLimit,omitzero"`
DisableSourceOfProjectReferenceRedirect Tristate `json:"disableSourceOfProjectReferenceRedirect,omitzero"`
DisableSolutionSearching Tristate `json:"disableSolutionSearching,omitzero"`
DisableReferencedProjectLoad Tristate `json:"disableReferencedProjectLoad,omitzero"`
ErasableSyntaxOnly Tristate `json:"erasableSyntaxOnly,omitzero"`
ESModuleInterop Tristate `json:"esModuleInterop,omitzero"`
ExactOptionalPropertyTypes Tristate `json:"exactOptionalPropertyTypes,omitzero"`
ExperimentalDecorators Tristate `json:"experimentalDecorators,omitzero"`
ForceConsistentCasingInFileNames Tristate `json:"forceConsistentCasingInFileNames,omitzero"`
IsolatedModules Tristate `json:"isolatedModules,omitzero"`
IsolatedDeclarations Tristate `json:"isolatedDeclarations,omitzero"`
IgnoreDeprecations string `json:"ignoreDeprecations,omitzero"`
ImportHelpers Tristate `json:"importHelpers,omitzero"`
InlineSourceMap Tristate `json:"inlineSourceMap,omitzero"`
InlineSources Tristate `json:"inlineSources,omitzero"`
Init Tristate `json:"init,omitzero"`
Incremental Tristate `json:"incremental,omitzero"`
Jsx JsxEmit `json:"jsx,omitzero"`
JsxFactory string `json:"jsxFactory,omitzero"`
JsxFragmentFactory string `json:"jsxFragmentFactory,omitzero"`
JsxImportSource string `json:"jsxImportSource,omitzero"`
Lib []string `json:"lib,omitzero"`
LibReplacement Tristate `json:"libReplacement,omitzero"`
Locale string `json:"locale,omitzero"`
MapRoot string `json:"mapRoot,omitzero"`
Module ModuleKind `json:"module,omitzero"`
ModuleResolution ModuleResolutionKind `json:"moduleResolution,omitzero"`
ModuleSuffixes []string `json:"moduleSuffixes,omitzero"`
ModuleDetection ModuleDetectionKind `json:"moduleDetection,omitzero"`
NewLine NewLineKind `json:"newLine,omitzero"`
NoEmit Tristate `json:"noEmit,omitzero"`
NoCheck Tristate `json:"noCheck,omitzero"`
NoErrorTruncation Tristate `json:"noErrorTruncation,omitzero"`
NoFallthroughCasesInSwitch Tristate `json:"noFallthroughCasesInSwitch,omitzero"`
NoImplicitAny Tristate `json:"noImplicitAny,omitzero"`
NoImplicitThis Tristate `json:"noImplicitThis,omitzero"`
NoImplicitReturns Tristate `json:"noImplicitReturns,omitzero"`
NoEmitHelpers Tristate `json:"noEmitHelpers,omitzero"`
NoLib Tristate `json:"noLib,omitzero"`
NoPropertyAccessFromIndexSignature Tristate `json:"noPropertyAccessFromIndexSignature,omitzero"`
NoUncheckedIndexedAccess Tristate `json:"noUncheckedIndexedAccess,omitzero"`
NoEmitOnError Tristate `json:"noEmitOnError,omitzero"`
NoUnusedLocals Tristate `json:"noUnusedLocals,omitzero"`
NoUnusedParameters Tristate `json:"noUnusedParameters,omitzero"`
NoResolve Tristate `json:"noResolve,omitzero"`
NoImplicitOverride Tristate `json:"noImplicitOverride,omitzero"`
NoUncheckedSideEffectImports Tristate `json:"noUncheckedSideEffectImports,omitzero"`
OutDir string `json:"outDir,omitzero"`
Paths *collections.OrderedMap[string, []string] `json:"paths,omitzero"`
PreserveConstEnums Tristate `json:"preserveConstEnums,omitzero"`
PreserveSymlinks Tristate `json:"preserveSymlinks,omitzero"`
Project string `json:"project,omitzero"`
ResolveJsonModule Tristate `json:"resolveJsonModule,omitzero"`
ResolvePackageJsonExports Tristate `json:"resolvePackageJsonExports,omitzero"`
ResolvePackageJsonImports Tristate `json:"resolvePackageJsonImports,omitzero"`
RemoveComments Tristate `json:"removeComments,omitzero"`
RewriteRelativeImportExtensions Tristate `json:"rewriteRelativeImportExtensions,omitzero"`
ReactNamespace string `json:"reactNamespace,omitzero"`
RootDir string `json:"rootDir,omitzero"`
RootDirs []string `json:"rootDirs,omitzero"`
SkipLibCheck Tristate `json:"skipLibCheck,omitzero"`
Strict Tristate `json:"strict,omitzero"`
StrictBindCallApply Tristate `json:"strictBindCallApply,omitzero"`
StrictBuiltinIteratorReturn Tristate `json:"strictBuiltinIteratorReturn,omitzero"`
StrictFunctionTypes Tristate `json:"strictFunctionTypes,omitzero"`
StrictNullChecks Tristate `json:"strictNullChecks,omitzero"`
StrictPropertyInitialization Tristate `json:"strictPropertyInitialization,omitzero"`
StripInternal Tristate `json:"stripInternal,omitzero"`
SkipDefaultLibCheck Tristate `json:"skipDefaultLibCheck,omitzero"`
SourceMap Tristate `json:"sourceMap,omitzero"`
SourceRoot string `json:"sourceRoot,omitzero"`
SuppressOutputPathCheck Tristate `json:"suppressOutputPathCheck,omitzero"`
Target ScriptTarget `json:"target,omitzero"`
TraceResolution Tristate `json:"traceResolution,omitzero"`
TsBuildInfoFile string `json:"tsBuildInfoFile,omitzero"`
TypeRoots []string `json:"typeRoots,omitzero"`
Types []string `json:"types,omitzero"`
UseDefineForClassFields Tristate `json:"useDefineForClassFields,omitzero"`
UseUnknownInCatchVariables Tristate `json:"useUnknownInCatchVariables,omitzero"`
VerbatimModuleSyntax Tristate `json:"verbatimModuleSyntax,omitzero"`
MaxNodeModuleJsDepth *int `json:"maxNodeModuleJsDepth,omitzero"`
// Deprecated: Do not use outside of options parsing and validation.
BaseUrl string `json:"baseUrl,omitzero"`
// Deprecated: Do not use outside of options parsing and validation.
OutFile string `json:"outFile,omitzero"`
// Internal fields
ConfigFilePath string `json:"configFilePath,omitzero"`
NoDtsResolution Tristate `json:"noDtsResolution,omitzero"`
PathsBasePath string `json:"pathsBasePath,omitzero"`
Diagnostics Tristate `json:"diagnostics,omitzero"`
ExtendedDiagnostics Tristate `json:"extendedDiagnostics,omitzero"`
GenerateCpuProfile string `json:"generateCpuProfile,omitzero"`
GenerateTrace string `json:"generateTrace,omitzero"`
ListEmittedFiles Tristate `json:"listEmittedFiles,omitzero"`
ListFiles Tristate `json:"listFiles,omitzero"`
ExplainFiles Tristate `json:"explainFiles,omitzero"`
ListFilesOnly Tristate `json:"listFilesOnly,omitzero"`
NoEmitForJsFiles Tristate `json:"noEmitForJsFiles,omitzero"`
PreserveWatchOutput Tristate `json:"preserveWatchOutput,omitzero"`
Pretty Tristate `json:"pretty,omitzero"`
Version Tristate `json:"version,omitzero"`
Watch Tristate `json:"watch,omitzero"`
ShowConfig Tristate `json:"showConfig,omitzero"`
Build Tristate `json:"build,omitzero"`
Help Tristate `json:"help,omitzero"`
All Tristate `json:"all,omitzero"`
PprofDir string `json:"pprofDir,omitzero"`
SingleThreaded Tristate `json:"singleThreaded,omitzero"`
Quiet Tristate `json:"quiet,omitzero"`
sourceFileAffectingCompilerOptionsOnce sync.Once
sourceFileAffectingCompilerOptions SourceFileAffectingCompilerOptions
}
// noCopy may be embedded into structs which must not be copied
// after the first use.
//
// See https://golang.org/issues/8005#issuecomment-190753527
// for details.
type noCopy struct{}
// Lock is a no-op used by -copylocks checker from `go vet`.
func (*noCopy) Lock() {}
func (*noCopy) Unlock() {}
var optionsType = reflect.TypeFor[CompilerOptions]()
// Clone creates a shallow copy of the CompilerOptions.
func (options *CompilerOptions) Clone() *CompilerOptions {
// TODO: this could be generated code instead of reflection.
target := &CompilerOptions{}
sourceValue := reflect.ValueOf(options).Elem()
targetValue := reflect.ValueOf(target).Elem()
for i := range sourceValue.NumField() {
if optionsType.Field(i).IsExported() {
targetValue.Field(i).Set(sourceValue.Field(i))
}
}
return target
}
func (options *CompilerOptions) GetEmitScriptTarget() ScriptTarget {
if options.Target != ScriptTargetNone {
return options.Target
}
switch options.GetEmitModuleKind() {
case ModuleKindNode16, ModuleKindNode18:
return ScriptTargetES2022
case ModuleKindNode20:
return ScriptTargetES2023
case ModuleKindNodeNext:
return ScriptTargetESNext
default:
return ScriptTargetES5
}
}
func (options *CompilerOptions) GetEmitModuleKind() ModuleKind {
if options.Module != ModuleKindNone {
return options.Module
}
if options.Target >= ScriptTargetES2015 {
return ModuleKindES2015
}
return ModuleKindCommonJS
}
func (options *CompilerOptions) GetModuleResolutionKind() ModuleResolutionKind {
if options.ModuleResolution != ModuleResolutionKindUnknown {
return options.ModuleResolution
}
switch options.GetEmitModuleKind() {
case ModuleKindNode16, ModuleKindNode18, ModuleKindNode20:
return ModuleResolutionKindNode16
case ModuleKindNodeNext:
return ModuleResolutionKindNodeNext
default:
return ModuleResolutionKindBundler
}
}
func (options *CompilerOptions) GetEmitModuleDetectionKind() ModuleDetectionKind {
if options.ModuleDetection != ModuleDetectionKindNone {
return options.ModuleDetection
}
switch options.GetEmitModuleKind() {
case ModuleKindNode16, ModuleKindNode20, ModuleKindNodeNext:
return ModuleDetectionKindForce
default:
return ModuleDetectionKindAuto
}
}
func (options *CompilerOptions) GetResolvePackageJsonExports() bool {
return options.ResolvePackageJsonExports.IsTrueOrUnknown()
}
func (options *CompilerOptions) GetResolvePackageJsonImports() bool {
return options.ResolvePackageJsonImports.IsTrueOrUnknown()
}
func (options *CompilerOptions) GetAllowImportingTsExtensions() bool {
return options.AllowImportingTsExtensions.IsTrue() || options.RewriteRelativeImportExtensions.IsTrue()
}
func (options *CompilerOptions) AllowImportingTsExtensionsFrom(fileName string) bool {
return options.GetAllowImportingTsExtensions() || tspath.IsDeclarationFileName(fileName)
}
func (options *CompilerOptions) GetESModuleInterop() bool {
if options.ESModuleInterop != TSUnknown {
return options.ESModuleInterop == TSTrue
}
switch options.GetEmitModuleKind() {
case ModuleKindNode16, ModuleKindNode18, ModuleKindNode20, ModuleKindNodeNext, ModuleKindPreserve:
return true
}
return false
}
func (options *CompilerOptions) GetAllowSyntheticDefaultImports() bool {
if options.AllowSyntheticDefaultImports != TSUnknown {
return options.AllowSyntheticDefaultImports == TSTrue
}
return options.GetESModuleInterop() ||
options.GetEmitModuleKind() == ModuleKindSystem ||
options.GetModuleResolutionKind() == ModuleResolutionKindBundler
}
func (options *CompilerOptions) GetResolveJsonModule() bool {
if options.ResolveJsonModule != TSUnknown {
return options.ResolveJsonModule == TSTrue
}
switch options.GetEmitModuleKind() {
// TODO in 6.0: add Node16/Node18
case ModuleKindNode20, ModuleKindESNext:
return true
}
return options.GetModuleResolutionKind() == ModuleResolutionKindBundler
}
func (options *CompilerOptions) ShouldPreserveConstEnums() bool {
return options.PreserveConstEnums == TSTrue || options.GetIsolatedModules()
}
func (options *CompilerOptions) GetAllowJS() bool {
if options.AllowJs != TSUnknown {
return options.AllowJs == TSTrue
}
return options.CheckJs == TSTrue
}
func (options *CompilerOptions) GetJSXTransformEnabled() bool {
jsx := options.Jsx
return jsx == JsxEmitReact || jsx == JsxEmitReactJSX || jsx == JsxEmitReactJSXDev
}
func (options *CompilerOptions) GetStrictOptionValue(value Tristate) bool {
if value != TSUnknown {
return value == TSTrue
}
return options.Strict == TSTrue
}
func (options *CompilerOptions) GetEffectiveTypeRoots(currentDirectory string) (result []string, fromConfig bool) {
if options.TypeRoots != nil {
return options.TypeRoots, true
}
var baseDir string
if options.ConfigFilePath != "" {
baseDir = tspath.GetDirectoryPath(options.ConfigFilePath)
} else {
baseDir = currentDirectory
if baseDir == "" {
// This was accounted for in the TS codebase, but only for third-party API usage
// where the module resolution host does not provide a getCurrentDirectory().
panic("cannot get effective type roots without a config file path or current directory")
}
}
typeRoots := make([]string, 0, strings.Count(baseDir, "/"))
tspath.ForEachAncestorDirectory(baseDir, func(dir string) (any, bool) {
typeRoots = append(typeRoots, tspath.CombinePaths(dir, "node_modules", "@types"))
return nil, false
})
return typeRoots, false
}
func (options *CompilerOptions) GetIsolatedModules() bool {
return options.IsolatedModules == TSTrue || options.VerbatimModuleSyntax == TSTrue
}
func (options *CompilerOptions) IsIncremental() bool {
return options.Incremental.IsTrue() || options.Composite.IsTrue()
}
func (options *CompilerOptions) GetEmitStandardClassFields() bool {
return options.UseDefineForClassFields != TSFalse && options.GetEmitScriptTarget() >= ScriptTargetES2022
}
func (options *CompilerOptions) GetEmitDeclarations() bool {
return options.Declaration.IsTrue() || options.Composite.IsTrue()
}
func (options *CompilerOptions) GetAreDeclarationMapsEnabled() bool {
return options.DeclarationMap == TSTrue && options.GetEmitDeclarations()
}
func (options *CompilerOptions) HasJsonModuleEmitEnabled() bool {
switch options.GetEmitModuleKind() {
case ModuleKindNone, ModuleKindSystem, ModuleKindUMD:
return false
}
return true
}
func (options *CompilerOptions) GetPathsBasePath(currentDirectory string) string {
if options.Paths.Size() == 0 {
return ""
}
if options.PathsBasePath != "" {
return options.PathsBasePath
}
return currentDirectory
}
// SourceFileAffectingCompilerOptions are the precomputed CompilerOptions values which
// affect the parse and bind of a source file.
type SourceFileAffectingCompilerOptions struct {
AllowUnreachableCode Tristate
AllowUnusedLabels Tristate
BindInStrictMode bool
ShouldPreserveConstEnums bool
}
func (options *CompilerOptions) SourceFileAffecting() SourceFileAffectingCompilerOptions {
options.sourceFileAffectingCompilerOptionsOnce.Do(func() {
options.sourceFileAffectingCompilerOptions = SourceFileAffectingCompilerOptions{
AllowUnreachableCode: options.AllowUnreachableCode,
AllowUnusedLabels: options.AllowUnusedLabels,
BindInStrictMode: options.AlwaysStrict.IsTrue() || options.Strict.IsTrue(),
ShouldPreserveConstEnums: options.ShouldPreserveConstEnums(),
}
})
return options.sourceFileAffectingCompilerOptions
}
type ModuleDetectionKind int32
const (
ModuleDetectionKindNone ModuleDetectionKind = 0
ModuleDetectionKindAuto ModuleDetectionKind = 1
ModuleDetectionKindLegacy ModuleDetectionKind = 2
ModuleDetectionKindForce ModuleDetectionKind = 3
)
type ModuleKind int32
const (
ModuleKindNone ModuleKind = 0
ModuleKindCommonJS ModuleKind = 1
// Deprecated: Do not use outside of options parsing and validation.
ModuleKindAMD ModuleKind = 2
// Deprecated: Do not use outside of options parsing and validation.
ModuleKindUMD ModuleKind = 3
// Deprecated: Do not use outside of options parsing and validation.
ModuleKindSystem ModuleKind = 4
// NOTE: ES module kinds should be contiguous to more easily check whether a module kind is *any* ES module kind.
// Non-ES module kinds should not come between ES2015 (the earliest ES module kind) and ESNext (the last ES
// module kind).
ModuleKindES2015 ModuleKind = 5
ModuleKindES2020 ModuleKind = 6
ModuleKindES2022 ModuleKind = 7
ModuleKindESNext ModuleKind = 99
// Node16+ is an amalgam of commonjs (albeit updated) and es2022+, and represents a distinct module system from es2020/esnext
ModuleKindNode16 ModuleKind = 100
ModuleKindNode18 ModuleKind = 101
ModuleKindNode20 ModuleKind = 102
ModuleKindNodeNext ModuleKind = 199
// Emit as written
ModuleKindPreserve ModuleKind = 200
)
func (moduleKind ModuleKind) IsNonNodeESM() bool {
return moduleKind >= ModuleKindES2015 && moduleKind <= ModuleKindESNext
}
func (moduleKind ModuleKind) SupportsImportAttributes() bool {
return ModuleKindNode18 <= moduleKind && moduleKind <= ModuleKindNodeNext ||
moduleKind == ModuleKindPreserve ||
moduleKind == ModuleKindESNext
}
type ResolutionMode = ModuleKind // ModuleKindNone | ModuleKindCommonJS | ModuleKindESNext
const (
ResolutionModeNone = ModuleKindNone
ResolutionModeCommonJS = ModuleKindCommonJS
ResolutionModeESM = ModuleKindESNext
)
type ModuleResolutionKind int32
const (
ModuleResolutionKindUnknown ModuleResolutionKind = 0
// Starting with node16, node's module resolver has significant departures from traditional cjs resolution
// to better support ECMAScript modules and their use within node - however more features are still being added.
// TypeScript's Node ESM support was introduced after Node 12 went end-of-life, and Node 14 is the earliest stable
// version that supports both pattern trailers - *but*, Node 16 is the first version that also supports ECMAScript 2022.
// In turn, we offer both a `NodeNext` moving resolution target, and a `Node16` version-anchored resolution target
ModuleResolutionKindNode16 ModuleResolutionKind = 3
ModuleResolutionKindNodeNext ModuleResolutionKind = 99 // Not simply `Node16` so that compiled code linked against TS can use the `Next` value reliably (same as with `ModuleKind`)
ModuleResolutionKindBundler ModuleResolutionKind = 100
)
var ModuleKindToModuleResolutionKind = map[ModuleKind]ModuleResolutionKind{
ModuleKindNode16: ModuleResolutionKindNode16,
ModuleKindNodeNext: ModuleResolutionKindNodeNext,
}
// We don't use stringer on this for now, because these values
// are user-facing in --traceResolution, and stringer currently
// lacks the ability to remove the "ModuleResolutionKind" prefix
// when generating code for multiple types into the same output
// file. Additionally, since there's no TS equivalent of
// `ModuleResolutionKindUnknown`, we want to panic on that case,
// as it probably represents a mistake when porting TS to Go.
func (m ModuleResolutionKind) String() string {
switch m {
case ModuleResolutionKindUnknown:
panic("should not use zero value of ModuleResolutionKind")
case ModuleResolutionKindNode16:
return "Node16"
case ModuleResolutionKindNodeNext:
return "NodeNext"
case ModuleResolutionKindBundler:
return "Bundler"
default:
panic("unhandled case in ModuleResolutionKind.String")
}
}
type NewLineKind int32
const (
NewLineKindNone NewLineKind = 0
NewLineKindCRLF NewLineKind = 1
NewLineKindLF NewLineKind = 2
)
func GetNewLineKind(s string) NewLineKind {
switch s {
case "\r\n":
return NewLineKindCRLF
case "\n":
return NewLineKindLF
default:
return NewLineKindNone
}
}
func (newLine NewLineKind) GetNewLineCharacter() string {
switch newLine {
case NewLineKindCRLF:
return "\r\n"
default:
return "\n"
}
}
type ScriptTarget int32
const (
ScriptTargetNone ScriptTarget = 0
ScriptTargetES3 ScriptTarget = 0 // Deprecated
ScriptTargetES5 ScriptTarget = 1
ScriptTargetES2015 ScriptTarget = 2
ScriptTargetES2016 ScriptTarget = 3
ScriptTargetES2017 ScriptTarget = 4
ScriptTargetES2018 ScriptTarget = 5
ScriptTargetES2019 ScriptTarget = 6
ScriptTargetES2020 ScriptTarget = 7
ScriptTargetES2021 ScriptTarget = 8
ScriptTargetES2022 ScriptTarget = 9
ScriptTargetES2023 ScriptTarget = 10
ScriptTargetES2024 ScriptTarget = 11
ScriptTargetESNext ScriptTarget = 99
ScriptTargetJSON ScriptTarget = 100
ScriptTargetLatest ScriptTarget = ScriptTargetESNext
)
type JsxEmit int32
const (
JsxEmitNone JsxEmit = 0
JsxEmitPreserve JsxEmit = 1
JsxEmitReactNative JsxEmit = 2
JsxEmitReact JsxEmit = 3
JsxEmitReactJSX JsxEmit = 4
JsxEmitReactJSXDev JsxEmit = 5
)

View File

@ -0,0 +1,36 @@
package core
import (
"context"
"golang.org/x/text/language"
)
type key int
const (
requestIDKey key = iota
localeKey
)
func WithRequestID(ctx context.Context, id string) context.Context {
return context.WithValue(ctx, requestIDKey, id)
}
func GetRequestID(ctx context.Context) string {
if id, ok := ctx.Value(requestIDKey).(string); ok {
return id
}
return ""
}
func WithLocale(ctx context.Context, locale language.Tag) context.Context {
return context.WithValue(ctx, localeKey, locale)
}
func GetLocale(ctx context.Context) language.Tag {
if locale, ok := ctx.Value(localeKey).(language.Tag); ok {
return locale
}
return language.Und
}

View File

@ -0,0 +1,689 @@
package core
import (
"iter"
"maps"
"math"
"slices"
"sort"
"strings"
"sync"
"unicode"
"unicode/utf8"
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/debug"
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/jsonutil"
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/stringutil"
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/tspath"
)
func Filter[T any](slice []T, f func(T) bool) []T {
for i, value := range slice {
if !f(value) {
result := slices.Clone(slice[:i])
for i++; i < len(slice); i++ {
value = slice[i]
if f(value) {
result = append(result, value)
}
}
return result
}
}
return slice
}
func FilterIndex[T any](slice []T, f func(T, int, []T) bool) []T {
for i, value := range slice {
if !f(value, i, slice) {
result := slices.Clone(slice[:i])
for i++; i < len(slice); i++ {
value = slice[i]
if f(value, i, slice) {
result = append(result, value)
}
}
return result
}
}
return slice
}
func Map[T, U any](slice []T, f func(T) U) []U {
if slice == nil {
return nil
}
result := make([]U, len(slice))
for i, value := range slice {
result[i] = f(value)
}
return result
}
func TryMap[T, U any](slice []T, f func(T) (U, error)) ([]U, error) {
if len(slice) == 0 {
return nil, nil
}
result := make([]U, len(slice))
for i, value := range slice {
mapped, err := f(value)
if err != nil {
return nil, err
}
result[i] = mapped
}
return result, nil
}
func MapIndex[T, U any](slice []T, f func(T, int) U) []U {
if slice == nil {
return nil
}
result := make([]U, len(slice))
for i, value := range slice {
result[i] = f(value, i)
}
return result
}
func MapNonNil[T any, U comparable](slice []T, f func(T) U) []U {
var result []U
for _, value := range slice {
mapped := f(value)
if mapped != *new(U) {
result = append(result, mapped)
}
}
return result
}
func MapFiltered[T any, U any](slice []T, f func(T) (U, bool)) []U {
var result []U
for _, value := range slice {
mapped, ok := f(value)
if !ok {
continue
}
result = append(result, mapped)
}
return result
}
func FlatMap[T any, U comparable](slice []T, f func(T) []U) []U {
var result []U
for _, value := range slice {
mapped := f(value)
if len(mapped) != 0 {
result = append(result, mapped...)
}
}
return result
}
func SameMap[T comparable](slice []T, f func(T) T) []T {
for i, value := range slice {
mapped := f(value)
if mapped != value {
result := make([]T, len(slice))
copy(result, slice[:i])
result[i] = mapped
for j := i + 1; j < len(slice); j++ {
result[j] = f(slice[j])
}
return result
}
}
return slice
}
func SameMapIndex[T comparable](slice []T, f func(T, int) T) []T {
for i, value := range slice {
mapped := f(value, i)
if mapped != value {
result := make([]T, len(slice))
copy(result, slice[:i])
result[i] = mapped
for j := i + 1; j < len(slice); j++ {
result[j] = f(slice[j], j)
}
return result
}
}
return slice
}
func Same[T any](s1 []T, s2 []T) bool {
if len(s1) == len(s2) {
return len(s1) == 0 || &s1[0] == &s2[0]
}
return false
}
func Some[T any](slice []T, f func(T) bool) bool {
for _, value := range slice {
if f(value) {
return true
}
}
return false
}
func Every[T any](slice []T, f func(T) bool) bool {
for _, value := range slice {
if !f(value) {
return false
}
}
return true
}
func Or[T any](funcs ...func(T) bool) func(T) bool {
return func(input T) bool {
for _, f := range funcs {
if f(input) {
return true
}
}
return false
}
}
func Find[T any](slice []T, f func(T) bool) T {
for _, value := range slice {
if f(value) {
return value
}
}
return *new(T)
}
func FindLast[T any](slice []T, f func(T) bool) T {
for i := len(slice) - 1; i >= 0; i-- {
value := slice[i]
if f(value) {
return value
}
}
return *new(T)
}
func FindIndex[T any](slice []T, f func(T) bool) int {
for i, value := range slice {
if f(value) {
return i
}
}
return -1
}
func FindLastIndex[T any](slice []T, f func(T) bool) int {
for i := len(slice) - 1; i >= 0; i-- {
value := slice[i]
if f(value) {
return i
}
}
return -1
}
func FirstOrNil[T any](slice []T) T {
if len(slice) != 0 {
return slice[0]
}
return *new(T)
}
func LastOrNil[T any](slice []T) T {
if len(slice) != 0 {
return slice[len(slice)-1]
}
return *new(T)
}
func ElementOrNil[T any](slice []T, index int) T {
if index < len(slice) {
return slice[index]
}
return *new(T)
}
func FirstOrNilSeq[T any](seq iter.Seq[T]) T {
if seq != nil {
for value := range seq {
return value
}
}
return *new(T)
}
func FirstNonNil[T any, U comparable](slice []T, f func(T) U) U {
for _, value := range slice {
mapped := f(value)
if mapped != *new(U) {
return mapped
}
}
return *new(U)
}
func Concatenate[T any](s1 []T, s2 []T) []T {
if len(s2) == 0 {
return s1
}
if len(s1) == 0 {
return s2
}
return slices.Concat(s1, s2)
}
func Splice[T any](s1 []T, start int, deleteCount int, items ...T) []T {
if start < 0 {
start = len(s1) + start
}
if start < 0 {
start = 0
}
if start > len(s1) {
start = len(s1)
}
if deleteCount < 0 {
deleteCount = 0
}
end := min(start+max(deleteCount, 0), len(s1))
if start == end && len(items) == 0 {
return s1
}
return slices.Concat(s1[:start], items, s1[end:])
}
func CountWhere[T any](slice []T, f func(T) bool) int {
count := 0
for _, value := range slice {
if f(value) {
count++
}
}
return count
}
func ReplaceElement[T any](slice []T, i int, t T) []T {
result := slices.Clone(slice)
result[i] = t
return result
}
func InsertSorted[T any](slice []T, element T, cmp func(T, T) int) []T {
i, _ := slices.BinarySearchFunc(slice, element, cmp)
return slices.Insert(slice, i, element)
}
func AppendIfUnique[T comparable](slice []T, element T) []T {
if slices.Contains(slice, element) {
return slice
}
return append(slice, element)
}
func Memoize[T any](create func() T) func() T {
var value T
return func() T {
if create != nil {
value = create()
create = nil
}
return value
}
}
// Returns whenTrue if b is true; otherwise, returns whenFalse. IfElse should only be used when branches are either
// constant or precomputed as both branches will be evaluated regardless as to the value of b.
func IfElse[T any](b bool, whenTrue T, whenFalse T) T {
if b {
return whenTrue
}
return whenFalse
}
// Returns value if value is not the zero value of T; Otherwise, returns defaultValue. OrElse should only be used when
// defaultValue is constant or precomputed as its argument will be evaluated regardless as to the content of value.
func OrElse[T comparable](value T, defaultValue T) T {
if value != *new(T) {
return value
}
return defaultValue
}
// Returns `a` if `a` is not `nil`; Otherwise, returns `b`. Coalesce is roughly analogous to `??` in JS, except that it
// non-shortcutting, so it is advised to only use a constant or precomputed value for `b`
func Coalesce[T *U, U any](a T, b T) T {
if a == nil {
return b
} else {
return a
}
}
type ECMALineStarts []TextPos
func ComputeECMALineStarts(text string) ECMALineStarts {
result := make([]TextPos, 0, strings.Count(text, "\n")+1)
return slices.AppendSeq(result, ComputeECMALineStartsSeq(text))
}
func ComputeECMALineStartsSeq(text string) iter.Seq[TextPos] {
return func(yield func(TextPos) bool) {
textLen := TextPos(len(text))
var pos TextPos
var lineStart TextPos
for pos < textLen {
b := text[pos]
if b < utf8.RuneSelf {
pos++
switch b {
case '\r':
if pos < textLen && text[pos] == '\n' {
pos++
}
fallthrough
case '\n':
if !yield(lineStart) {
return
}
lineStart = pos
}
} else {
ch, size := utf8.DecodeRuneInString(text[pos:])
pos += TextPos(size)
if stringutil.IsLineBreak(ch) {
if !yield(lineStart) {
return
}
lineStart = pos
}
}
}
yield(lineStart)
}
}
func PositionToLineAndCharacter(position int, lineStarts []TextPos) (line int, character int) {
line = sort.Search(len(lineStarts), func(i int) bool {
return int(lineStarts[i]) > position
}) - 1
if line < 0 {
line = 0
}
return line, position - int(lineStarts[line])
}
func Flatten[T any](array [][]T) []T {
var result []T
for _, subArray := range array {
result = append(result, subArray...)
}
return result
}
func Must[T any](v T, err error) T {
if err != nil {
panic(err)
}
return v
}
// Extracts the first value of a multi-value return.
func FirstResult[T1 any](t1 T1, _ ...any) T1 {
return t1
}
func StringifyJson(input any, prefix string, indent string) (string, error) {
output, err := jsonutil.MarshalIndent(input, prefix, indent)
return string(output), err
}
func GetScriptKindFromFileName(fileName string) ScriptKind {
dotPos := strings.LastIndex(fileName, ".")
if dotPos >= 0 {
switch strings.ToLower(fileName[dotPos:]) {
case tspath.ExtensionJs, tspath.ExtensionCjs, tspath.ExtensionMjs:
return ScriptKindJS
case tspath.ExtensionJsx:
return ScriptKindJSX
case tspath.ExtensionTs, tspath.ExtensionCts, tspath.ExtensionMts:
return ScriptKindTS
case tspath.ExtensionTsx:
return ScriptKindTSX
case tspath.ExtensionJson:
return ScriptKindJSON
}
}
return ScriptKindUnknown
}
// Given a name and a list of names that are *not* equal to the name, return a spelling suggestion if there is one that is close enough.
// Names less than length 3 only check for case-insensitive equality.
//
// find the candidate with the smallest Levenshtein distance,
//
// except for candidates:
// * With no name
// * Whose length differs from the target name by more than 0.34 of the length of the name.
// * Whose levenshtein distance is more than 0.4 of the length of the name
// (0.4 allows 1 substitution/transposition for every 5 characters,
// and 1 insertion/deletion at 3 characters)
//
// @internal
func GetSpellingSuggestion[T any](name string, candidates []T, getName func(T) string) T {
maximumLengthDifference := max(2, int(float64(len(name))*0.34))
bestDistance := math.Floor(float64(len(name))*0.4) + 1 // If the best result is worse than this, don't bother.
runeName := []rune(name)
buffers := levenshteinBuffersPool.Get().(*levenshteinBuffers)
defer levenshteinBuffersPool.Put(buffers)
var bestCandidate T
for _, candidate := range candidates {
candidateName := getName(candidate)
maxLen := max(len(candidateName), len(name))
minLen := min(len(candidateName), len(name))
if candidateName != "" && maxLen-minLen <= maximumLengthDifference {
if candidateName == name {
continue
}
// Only consider candidates less than 3 characters long when they differ by case.
// Otherwise, don't bother, since a user would usually notice differences of a 2-character name.
if len(candidateName) < 3 && !strings.EqualFold(candidateName, name) {
continue
}
distance := levenshteinWithMax(buffers, runeName, []rune(candidateName), bestDistance-0.1)
if distance < 0 {
continue
}
debug.Assert(distance < bestDistance) // Else `levenshteinWithMax` should return undefined
bestDistance = distance
bestCandidate = candidate
}
}
return bestCandidate
}
type levenshteinBuffers struct {
previous []float64
current []float64
}
var levenshteinBuffersPool = sync.Pool{
New: func() any {
return &levenshteinBuffers{}
},
}
func levenshteinWithMax(buffers *levenshteinBuffers, s1 []rune, s2 []rune, maxValue float64) float64 {
bufferSize := len(s2) + 1
buffers.previous = slices.Grow(buffers.previous[:0], bufferSize)[:bufferSize]
buffers.current = slices.Grow(buffers.current[:0], bufferSize)[:bufferSize]
previous := buffers.previous
current := buffers.current
big := maxValue + 0.01
for i := range previous {
previous[i] = float64(i)
}
for i := 1; i <= len(s1); i++ {
c1 := s1[i-1]
minJ := max(int(math.Ceil(float64(i)-maxValue)), 1)
maxJ := min(int(math.Floor(maxValue+float64(i))), len(s2))
colMin := float64(i)
current[0] = colMin
for j := 1; j < minJ; j++ {
current[j] = big
}
for j := minJ; j <= maxJ; j++ {
var substitutionDistance, dist float64
if unicode.ToLower(s1[i-1]) == unicode.ToLower(s2[j-1]) {
substitutionDistance = previous[j-1] + 0.1
} else {
substitutionDistance = previous[j-1] + 2
}
if c1 == s2[j-1] {
dist = previous[j-1]
} else {
dist = math.Min(previous[j]+1, math.Min(current[j-1]+1, substitutionDistance))
}
current[j] = dist
colMin = math.Min(colMin, dist)
}
for j := maxJ + 1; j <= len(s2); j++ {
current[j] = big
}
if colMin > maxValue {
// Give up -- everything in this column is > max and it can't get better in future columns.
return -1
}
previous, current = current, previous
}
res := previous[len(s2)]
if res > maxValue {
return -1
}
return res
}
func Identity[T any](t T) T {
return t
}
func CheckEachDefined[S any](s []*S, msg string) []*S {
for _, value := range s {
if value == nil {
panic(msg)
}
}
return s
}
func IndexAfter(s string, pattern string, startIndex int) int {
matched := strings.Index(s[startIndex:], pattern)
if matched == -1 {
return -1
} else {
return matched + startIndex
}
}
func ShouldRewriteModuleSpecifier(specifier string, compilerOptions *CompilerOptions) bool {
return compilerOptions.RewriteRelativeImportExtensions.IsTrue() && tspath.PathIsRelative(specifier) && !tspath.IsDeclarationFileName(specifier) && tspath.HasTSFileExtension(specifier)
}
func SingleElementSlice[T any](element *T) []*T {
if element == nil {
return nil
}
return []*T{element}
}
func ConcatenateSeq[T any](seqs ...iter.Seq[T]) iter.Seq[T] {
return func(yield func(T) bool) {
for _, seq := range seqs {
if seq == nil {
continue
}
for e := range seq {
if !yield(e) {
return
}
}
}
}
}
func comparableValuesEqual[T comparable](a, b T) bool {
return a == b
}
func DiffMaps[K comparable, V comparable](m1 map[K]V, m2 map[K]V, onAdded func(K, V), onRemoved func(K, V), onChanged func(K, V, V)) {
DiffMapsFunc(m1, m2, comparableValuesEqual, onAdded, onRemoved, onChanged)
}
func DiffMapsFunc[K comparable, V any](m1 map[K]V, m2 map[K]V, equalValues func(V, V) bool, onAdded func(K, V), onRemoved func(K, V), onChanged func(K, V, V)) {
for k, v2 := range m2 {
if _, ok := m1[k]; !ok {
onAdded(k, v2)
}
}
for k, v1 := range m1 {
if v2, ok := m2[k]; ok {
if !equalValues(v1, v2) {
onChanged(k, v1, v2)
}
} else {
onRemoved(k, v1)
}
}
}
// CopyMapInto is maps.Copy, unless dst is nil, in which case it clones and returns src.
// Use CopyMapInto anywhere you would use maps.Copy preceded by a nil check and map initialization.
func CopyMapInto[M1 ~map[K]V, M2 ~map[K]V, K comparable, V any](dst M1, src M2) map[K]V {
if dst == nil {
return maps.Clone(src)
}
maps.Copy(dst, src)
return dst
}
func Deduplicate[T comparable](slice []T) []T {
if len(slice) > 1 {
for i, value := range slice {
if slices.Contains(slice[:i], value) {
result := slices.Clone(slice[:i])
for i++; i < len(slice); i++ {
value = slice[i]
if !slices.Contains(result, value) {
result = append(result, value)
}
}
return result
}
}
}
return slice
}
func DeduplicateSorted[T any](slice []T, isEqual func(a, b T) bool) []T {
if len(slice) == 0 {
return slice
}
last := slice[0]
deduplicated := slice[:1]
for i := 1; i < len(slice); i++ {
next := slice[i]
if isEqual(last, next) {
continue
}
deduplicated = append(deduplicated, next)
last = next
}
return deduplicated
}

View File

@ -0,0 +1,11 @@
package core
//go:generate go tool golang.org/x/tools/cmd/stringer -type=LanguageVariant -output=languagevariant_stringer_generated.go
//go:generate go tool mvdan.cc/gofumpt -w languagevariant_stringer_generated.go
type LanguageVariant int32
const (
LanguageVariantStandard LanguageVariant = iota
LanguageVariantJSX
)

View File

@ -0,0 +1,24 @@
// Code generated by "stringer -type=LanguageVariant -output=languagevariant_stringer_generated.go"; DO NOT EDIT.
package core
import "strconv"
func _() {
// An "invalid array index" compiler error signifies that the constant values have changed.
// Re-run the stringer command to generate them again.
var x [1]struct{}
_ = x[LanguageVariantStandard-0]
_ = x[LanguageVariantJSX-1]
}
const _LanguageVariant_name = "LanguageVariantStandardLanguageVariantJSX"
var _LanguageVariant_index = [...]uint8{0, 23, 41}
func (i LanguageVariant) String() string {
if i < 0 || i >= LanguageVariant(len(_LanguageVariant_index)-1) {
return "LanguageVariant(" + strconv.FormatInt(int64(i), 10) + ")"
}
return _LanguageVariant_name[_LanguageVariant_index[i]:_LanguageVariant_index[i+1]]
}

View File

@ -0,0 +1,30 @@
package core
// Links store
type LinkStore[K comparable, V any] struct {
entries map[K]*V
pool Pool[V]
}
func (s *LinkStore[K, V]) Get(key K) *V {
value := s.entries[key]
if value != nil {
return value
}
if s.entries == nil {
s.entries = make(map[K]*V)
}
value = s.pool.New()
s.entries[key] = value
return value
}
func (s *LinkStore[K, V]) Has(key K) bool {
_, ok := s.entries[key]
return ok
}
func (s *LinkStore[K, V]) TryGet(key K) *V {
return s.entries[key]
}

View File

@ -0,0 +1,52 @@
// Code generated by "stringer -type=ModuleKind -trimprefix=ModuleKind -output=modulekind_stringer_generated.go"; DO NOT EDIT.
package core
import "strconv"
func _() {
// An "invalid array index" compiler error signifies that the constant values have changed.
// Re-run the stringer command to generate them again.
var x [1]struct{}
_ = x[ModuleKindNone-0]
_ = x[ModuleKindCommonJS-1]
_ = x[ModuleKindAMD-2]
_ = x[ModuleKindUMD-3]
_ = x[ModuleKindSystem-4]
_ = x[ModuleKindES2015-5]
_ = x[ModuleKindES2020-6]
_ = x[ModuleKindES2022-7]
_ = x[ModuleKindESNext-99]
_ = x[ModuleKindNode16-100]
_ = x[ModuleKindNode18-101]
_ = x[ModuleKindNode20-102]
_ = x[ModuleKindNodeNext-199]
_ = x[ModuleKindPreserve-200]
}
const (
_ModuleKind_name_0 = "NoneCommonJSAMDUMDSystemES2015ES2020ES2022"
_ModuleKind_name_1 = "ESNextNode16Node18Node20"
_ModuleKind_name_2 = "NodeNextPreserve"
)
var (
_ModuleKind_index_0 = [...]uint8{0, 4, 12, 15, 18, 24, 30, 36, 42}
_ModuleKind_index_1 = [...]uint8{0, 6, 12, 18, 24}
_ModuleKind_index_2 = [...]uint8{0, 8, 16}
)
func (i ModuleKind) String() string {
switch {
case 0 <= i && i <= 7:
return _ModuleKind_name_0[_ModuleKind_index_0[i]:_ModuleKind_index_0[i+1]]
case 99 <= i && i <= 102:
i -= 99
return _ModuleKind_name_1[_ModuleKind_index_1[i]:_ModuleKind_index_1[i+1]]
case 199 <= i && i <= 200:
i -= 199
return _ModuleKind_name_2[_ModuleKind_index_2[i]:_ModuleKind_index_2[i+1]]
default:
return "ModuleKind(" + strconv.FormatInt(int64(i), 10) + ")"
}
}

View File

@ -0,0 +1,88 @@
package core
import (
"maps"
"sync"
)
var UnprefixedNodeCoreModules = map[string]bool{
"assert": true,
"assert/strict": true,
"async_hooks": true,
"buffer": true,
"child_process": true,
"cluster": true,
"console": true,
"constants": true,
"crypto": true,
"dgram": true,
"diagnostics_channel": true,
"dns": true,
"dns/promises": true,
"domain": true,
"events": true,
"fs": true,
"fs/promises": true,
"http": true,
"http2": true,
"https": true,
"inspector": true,
"inspector/promises": true,
"module": true,
"net": true,
"os": true,
"path": true,
"path/posix": true,
"path/win32": true,
"perf_hooks": true,
"process": true,
"punycode": true,
"querystring": true,
"readline": true,
"readline/promises": true,
"repl": true,
"stream": true,
"stream/consumers": true,
"stream/promises": true,
"stream/web": true,
"string_decoder": true,
"sys": true,
"test/mock_loader": true,
"timers": true,
"timers/promises": true,
"tls": true,
"trace_events": true,
"tty": true,
"url": true,
"util": true,
"util/types": true,
"v8": true,
"vm": true,
"wasi": true,
"worker_threads": true,
"zlib": true,
}
var ExclusivelyPrefixedNodeCoreModules = map[string]bool{
"node:sea": true,
"node:sqlite": true,
"node:test": true,
"node:test/reporters": true,
}
var NodeCoreModules = sync.OnceValue(func() map[string]bool {
nodeCoreModules := make(map[string]bool, len(UnprefixedNodeCoreModules)*2+len(ExclusivelyPrefixedNodeCoreModules))
for unprefixed := range UnprefixedNodeCoreModules {
nodeCoreModules[unprefixed] = true
nodeCoreModules["node:"+unprefixed] = true
}
maps.Copy(nodeCoreModules, ExclusivelyPrefixedNodeCoreModules)
return nodeCoreModules
})
func NonRelativeModuleNameForTypingCache(moduleName string) string {
if NodeCoreModules()[moduleName] {
return "node"
}
return moduleName
}

View File

@ -0,0 +1,10 @@
package core
type ParsedOptions struct {
CompilerOptions *CompilerOptions `json:"compilerOptions"`
WatchOptions *WatchOptions `json:"watchOptions"`
TypeAcquisition *TypeAcquisition `json:"typeAcquisition"`
FileNames []string `json:"fileNames"`
ProjectReferences []*ProjectReference `json:"projectReferences"`
}

View File

@ -0,0 +1,52 @@
package core
import "strings"
type Pattern struct {
Text string
StarIndex int // -1 for exact match
}
func TryParsePattern(pattern string) Pattern {
starIndex := strings.Index(pattern, "*")
if starIndex == -1 || !strings.Contains(pattern[starIndex+1:], "*") {
return Pattern{Text: pattern, StarIndex: starIndex}
}
return Pattern{}
}
func (p *Pattern) IsValid() bool {
return p.StarIndex == -1 || p.StarIndex < len(p.Text)
}
func (p *Pattern) Matches(candidate string) bool {
if p.StarIndex == -1 {
return p.Text == candidate
}
return len(candidate) >= p.StarIndex &&
strings.HasPrefix(candidate, p.Text[:p.StarIndex]) &&
strings.HasSuffix(candidate, p.Text[p.StarIndex+1:])
}
func (p *Pattern) MatchedText(candidate string) string {
if !p.Matches(candidate) {
panic("candidate does not match pattern")
}
if p.StarIndex == -1 {
return ""
}
return candidate[p.StarIndex : len(candidate)-len(p.Text)+p.StarIndex+1]
}
func FindBestPatternMatch[T any](values []T, getPattern func(v T) Pattern, candidate string) T {
var bestPattern T
longestMatchPrefixLength := -1
for _, value := range values {
pattern := getPattern(value)
if (pattern.StarIndex == -1 || pattern.StarIndex > longestMatchPrefixLength) && pattern.Matches(candidate) {
bestPattern = value
longestMatchPrefixLength = pattern.StarIndex
}
}
return bestPattern
}

View File

@ -0,0 +1,66 @@
package core
import "slices"
// Pool allocator
type Pool[T any] struct {
data []T
}
// Allocate a single element in the pool and return a pointer to the element. If the pool is at capacity,
// a new pool of the next size up is allocated.
func (p *Pool[T]) New() *T {
if len(p.data) == cap(p.data) {
nextSize := nextPoolSize(len(p.data))
// Use the same trick as slices.Concat; Grow rounds up to the next size class.
p.data = slices.Grow[[]T](nil, nextSize)
}
index := len(p.data)
p.data = p.data[:index+1]
return &p.data[index]
}
// Allocate a slice of the given size in the pool. If the requested size is beyond the capacity of the pool
// and a pool of the next size up still wouldn't fit the slice, make a separate memory allocation for the slice.
// Otherwise, grow the pool if necessary and allocate a slice out of it. The length and capacity of the resulting
// slice are equal to the given size.
func (p *Pool[T]) NewSlice(size int) []T {
if size == 0 {
return nil
}
if len(p.data)+size > cap(p.data) {
nextSize := nextPoolSize(len(p.data))
if size > nextSize {
return make([]T, size)
}
// Use the same trick as slices.Concat; Grow rounds up to the next size class.
p.data = slices.Grow[[]T](nil, nextSize)
}
newLen := len(p.data) + size
slice := p.data[len(p.data):newLen:newLen]
p.data = p.data[:newLen]
return slice
}
func (p *Pool[T]) NewSlice1(t T) []T {
slice := p.NewSlice(1)
slice[0] = t
return slice
}
func (p *Pool[T]) Clone(t []T) []T {
if len(t) == 0 {
return nil
}
slice := p.NewSlice(len(t))
copy(slice, t)
return slice
}
func nextPoolSize(size int) int {
// This compiles down branch-free.
size = max(size, 1)
size = min(size*2, 256)
return size
}

View File

@ -0,0 +1,20 @@
package core
import "efprojects.com/kitten-ipc/kitcom/internal/tsgo/tspath"
type ProjectReference struct {
Path string
OriginalPath string
Circular bool
}
func ResolveProjectReferencePath(ref *ProjectReference) string {
return ResolveConfigFileNameOfProjectReference(ref.Path)
}
func ResolveConfigFileNameOfProjectReference(path string) string {
if tspath.FileExtensionIs(path, tspath.ExtensionJson) {
return path
}
return tspath.CombinePaths(path, "tsconfig.json")
}

View File

@ -0,0 +1,21 @@
package core
//go:generate go tool golang.org/x/tools/cmd/stringer -type=ScriptKind -output=scriptkind_stringer_generated.go
//go:generate go tool mvdan.cc/gofumpt -w scriptkind_stringer_generated.go
type ScriptKind int32
const (
ScriptKindUnknown ScriptKind = iota
ScriptKindJS
ScriptKindJSX
ScriptKindTS
ScriptKindTSX
ScriptKindExternal
ScriptKindJSON
/**
* Used on extensions that doesn't define the ScriptKind but the content defines it.
* Deferred extensions are going to be included in all project contexts.
*/
ScriptKindDeferred
)

View File

@ -0,0 +1,30 @@
// Code generated by "stringer -type=ScriptKind -output=scriptkind_stringer_generated.go"; DO NOT EDIT.
package core
import "strconv"
func _() {
// An "invalid array index" compiler error signifies that the constant values have changed.
// Re-run the stringer command to generate them again.
var x [1]struct{}
_ = x[ScriptKindUnknown-0]
_ = x[ScriptKindJS-1]
_ = x[ScriptKindJSX-2]
_ = x[ScriptKindTS-3]
_ = x[ScriptKindTSX-4]
_ = x[ScriptKindExternal-5]
_ = x[ScriptKindJSON-6]
_ = x[ScriptKindDeferred-7]
}
const _ScriptKind_name = "ScriptKindUnknownScriptKindJSScriptKindJSXScriptKindTSScriptKindTSXScriptKindExternalScriptKindJSONScriptKindDeferred"
var _ScriptKind_index = [...]uint8{0, 17, 29, 42, 54, 67, 85, 99, 117}
func (i ScriptKind) String() string {
if i < 0 || i >= ScriptKind(len(_ScriptKind_index)-1) {
return "ScriptKind(" + strconv.FormatInt(int64(i), 10) + ")"
}
return _ScriptKind_name[_ScriptKind_index[i]:_ScriptKind_index[i+1]]
}

View File

@ -0,0 +1,49 @@
// Code generated by "stringer -type=ScriptTarget -trimprefix=ScriptTarget -output=scripttarget_stringer_generated.go"; DO NOT EDIT.
package core
import "strconv"
func _() {
// An "invalid array index" compiler error signifies that the constant values have changed.
// Re-run the stringer command to generate them again.
var x [1]struct{}
_ = x[ScriptTargetNone-0]
_ = x[ScriptTargetES3-0]
_ = x[ScriptTargetES5-1]
_ = x[ScriptTargetES2015-2]
_ = x[ScriptTargetES2016-3]
_ = x[ScriptTargetES2017-4]
_ = x[ScriptTargetES2018-5]
_ = x[ScriptTargetES2019-6]
_ = x[ScriptTargetES2020-7]
_ = x[ScriptTargetES2021-8]
_ = x[ScriptTargetES2022-9]
_ = x[ScriptTargetES2023-10]
_ = x[ScriptTargetES2024-11]
_ = x[ScriptTargetESNext-99]
_ = x[ScriptTargetJSON-100]
_ = x[ScriptTargetLatest-99]
}
const (
_ScriptTarget_name_0 = "NoneES5ES2015ES2016ES2017ES2018ES2019ES2020ES2021ES2022ES2023ES2024"
_ScriptTarget_name_1 = "ESNextJSON"
)
var (
_ScriptTarget_index_0 = [...]uint8{0, 4, 7, 13, 19, 25, 31, 37, 43, 49, 55, 61, 67}
_ScriptTarget_index_1 = [...]uint8{0, 6, 10}
)
func (i ScriptTarget) String() string {
switch {
case 0 <= i && i <= 11:
return _ScriptTarget_name_0[_ScriptTarget_index_0[i]:_ScriptTarget_index_0[i+1]]
case 99 <= i && i <= 100:
i -= 99
return _ScriptTarget_name_1[_ScriptTarget_index_1[i]:_ScriptTarget_index_1[i+1]]
default:
return "ScriptTarget(" + strconv.FormatInt(int64(i), 10) + ")"
}
}

View File

@ -0,0 +1,33 @@
package core
type Stack[T any] struct {
data []T
}
func (s *Stack[T]) Push(item T) {
s.data = append(s.data, item)
}
func (s *Stack[T]) Pop() T {
l := len(s.data)
if l == 0 {
panic("stack is empty")
}
item := s.data[l-1]
var zero T
s.data[l-1] = zero
s.data = s.data[:l-1]
return item
}
func (s *Stack[T]) Peek() T {
l := len(s.data)
if l == 0 {
panic("stack is empty")
}
return s.data[l-1]
}
func (s *Stack[T]) Len() int {
return len(s.data)
}

View File

@ -0,0 +1,66 @@
package core
// TextPos
type TextPos int32
// TextRange
type TextRange struct {
pos TextPos
end TextPos
}
func NewTextRange(pos int, end int) TextRange {
return TextRange{pos: TextPos(pos), end: TextPos(end)}
}
func UndefinedTextRange() TextRange {
return TextRange{pos: TextPos(-1), end: TextPos(-1)}
}
func (t TextRange) Pos() int {
return int(t.pos)
}
func (t TextRange) End() int {
return int(t.end)
}
func (t TextRange) Len() int {
return int(t.end - t.pos)
}
func (t TextRange) IsValid() bool {
return t.pos >= 0 || t.end >= 0
}
func (t TextRange) Contains(pos int) bool {
return pos >= int(t.pos) && pos < int(t.end)
}
func (t TextRange) ContainsInclusive(pos int) bool {
return pos >= int(t.pos) && pos <= int(t.end)
}
func (t TextRange) ContainsExclusive(pos int) bool {
return int(t.pos) < pos && pos < int(t.end)
}
func (t TextRange) WithPos(pos int) TextRange {
return TextRange{pos: TextPos(pos), end: t.end}
}
func (t TextRange) WithEnd(end int) TextRange {
return TextRange{pos: t.pos, end: TextPos(end)}
}
func (t TextRange) ContainedBy(t2 TextRange) bool {
return t2.pos <= t.pos && t2.end >= t.end
}
func (t TextRange) Overlaps(t2 TextRange) bool {
start := max(t.pos, t2.pos)
end := min(t.end, t2.end)
return start < end
}

View File

@ -0,0 +1,30 @@
package core
import "strings"
type TextChange struct {
TextRange
NewText string
}
func (t TextChange) ApplyTo(text string) string {
return text[:t.Pos()] + t.NewText + text[t.End():]
}
func ApplyBulkEdits(text string, edits []TextChange) string {
b := strings.Builder{}
b.Grow(len(text))
lastEnd := 0
for _, e := range edits {
start := e.TextRange.Pos()
if start != lastEnd {
b.WriteString(text[lastEnd:e.TextRange.Pos()])
}
b.WriteString(e.NewText)
lastEnd = e.TextRange.End()
}
b.WriteString(text[lastEnd:])
return b.String()
}

View File

@ -0,0 +1,71 @@
package core
//go:generate go tool golang.org/x/tools/cmd/stringer -type=Tristate -output=tristate_stringer_generated.go
//go:generate go tool mvdan.cc/gofumpt -w tristate_stringer_generated.go
// Tristate
type Tristate byte
const (
TSUnknown Tristate = iota
TSFalse
TSTrue
)
func (t Tristate) IsTrue() bool {
return t == TSTrue
}
func (t Tristate) IsTrueOrUnknown() bool {
return t == TSTrue || t == TSUnknown
}
func (t Tristate) IsFalse() bool {
return t == TSFalse
}
func (t Tristate) IsFalseOrUnknown() bool {
return t == TSFalse || t == TSUnknown
}
func (t Tristate) IsUnknown() bool {
return t == TSUnknown
}
func (t Tristate) DefaultIfUnknown(value Tristate) Tristate {
if t == TSUnknown {
return value
}
return t
}
func (t *Tristate) UnmarshalJSON(data []byte) error {
switch string(data) {
case "true":
*t = TSTrue
case "false":
*t = TSFalse
default:
*t = TSUnknown
}
return nil
}
func (t Tristate) MarshalJSON() ([]byte, error) {
switch t {
case TSTrue:
return []byte("true"), nil
case TSFalse:
return []byte("false"), nil
default:
return []byte("null"), nil
}
}
func BoolToTristate(b bool) Tristate {
if b {
return TSTrue
}
return TSFalse
}

View File

@ -0,0 +1,25 @@
// Code generated by "stringer -type=Tristate -output=tristate_stringer_generated.go"; DO NOT EDIT.
package core
import "strconv"
func _() {
// An "invalid array index" compiler error signifies that the constant values have changed.
// Re-run the stringer command to generate them again.
var x [1]struct{}
_ = x[TSUnknown-0]
_ = x[TSFalse-1]
_ = x[TSTrue-2]
}
const _Tristate_name = "TSUnknownTSFalseTSTrue"
var _Tristate_index = [...]uint8{0, 9, 16, 22}
func (i Tristate) String() string {
if i >= Tristate(len(_Tristate_index)-1) {
return "Tristate(" + strconv.FormatInt(int64(i), 10) + ")"
}
return _Tristate_name[_Tristate_index[i]:_Tristate_index[i+1]]
}

View File

@ -0,0 +1,24 @@
package core
import "slices"
type TypeAcquisition struct {
Enable Tristate `json:"enable,omitzero"`
Include []string `json:"include,omitzero"`
Exclude []string `json:"exclude,omitzero"`
DisableFilenameBasedTypeAcquisition Tristate `json:"disableFilenameBasedTypeAcquisition,omitzero"`
}
func (ta *TypeAcquisition) Equals(other *TypeAcquisition) bool {
if ta == other {
return true
}
if ta == nil || other == nil {
return false
}
return (ta.Enable == other.Enable &&
slices.Equal(ta.Include, other.Include) &&
slices.Equal(ta.Exclude, other.Exclude) &&
ta.DisableFilenameBasedTypeAcquisition == other.DisableFilenameBasedTypeAcquisition)
}

View File

@ -0,0 +1,33 @@
package core
import (
"strings"
)
// This is a var so it can be overridden by ldflags.
var version = "7.0.0-dev"
func Version() string {
return version
}
var versionMajorMinor = func() string {
seenMajor := false
i := strings.IndexFunc(version, func(r rune) bool {
if r == '.' {
if seenMajor {
return true
}
seenMajor = true
}
return false
})
if i == -1 {
panic("invalid version string: " + version)
}
return version[:i]
}()
func VersionMajorMinor() string {
return versionMajorMinor
}

View File

@ -0,0 +1,53 @@
package core
import "time"
type WatchOptions struct {
Interval *int `json:"watchInterval"`
FileKind WatchFileKind `json:"watchFile"`
DirectoryKind WatchDirectoryKind `json:"watchDirectory"`
FallbackPolling PollingKind `json:"fallbackPolling"`
SyncWatchDir Tristate `json:"synchronousWatchDirectory"`
ExcludeDir []string `json:"excludeDirectories"`
ExcludeFiles []string `json:"excludeFiles"`
}
type WatchFileKind int32
const (
WatchFileKindNone WatchFileKind = 0
WatchFileKindFixedPollingInterval WatchFileKind = 1
WatchFileKindPriorityPollingInterval WatchFileKind = 2
WatchFileKindDynamicPriorityPolling WatchFileKind = 3
WatchFileKindFixedChunkSizePolling WatchFileKind = 4
WatchFileKindUseFsEvents WatchFileKind = 5
WatchFileKindUseFsEventsOnParentDirectory WatchFileKind = 6
)
type WatchDirectoryKind int32
const (
WatchDirectoryKindNone WatchDirectoryKind = 0
WatchDirectoryKindUseFsEvents WatchDirectoryKind = 1
WatchDirectoryKindFixedPollingInterval WatchDirectoryKind = 2
WatchDirectoryKindDynamicPriorityPolling WatchDirectoryKind = 3
WatchDirectoryKindFixedChunkSizePolling WatchDirectoryKind = 4
)
type PollingKind int32
const (
PollingKindNone PollingKind = 0
PollingKindFixedInterval PollingKind = 1
PollingKindPriorityInterval PollingKind = 2
PollingKindDynamicPriority PollingKind = 3
PollingKindFixedChunkSize PollingKind = 4
)
func (w *WatchOptions) WatchInterval() time.Duration {
watchInterval := 1000 * time.Millisecond
if w != nil && w.Interval != nil {
watchInterval = time.Duration(*w.Interval) * time.Millisecond
}
return watchInterval
}

View File

@ -0,0 +1,125 @@
package core
import (
"context"
"sync"
"sync/atomic"
"golang.org/x/sync/errgroup"
)
type WorkGroup interface {
// Queue queues a function to run. It may be invoked immediately, or deferred until RunAndWait.
// It is not safe to call Queue after RunAndWait has returned.
Queue(fn func())
// RunAndWait runs all queued functions, blocking until they have all completed.
RunAndWait()
}
func NewWorkGroup(singleThreaded bool) WorkGroup {
if singleThreaded {
return &singleThreadedWorkGroup{}
}
return &parallelWorkGroup{}
}
type parallelWorkGroup struct {
done atomic.Bool
wg sync.WaitGroup
}
var _ WorkGroup = (*parallelWorkGroup)(nil)
func (w *parallelWorkGroup) Queue(fn func()) {
if w.done.Load() {
panic("Queue called after RunAndWait returned")
}
w.wg.Add(1)
go func() {
defer w.wg.Done()
fn()
}()
}
func (w *parallelWorkGroup) RunAndWait() {
defer w.done.Store(true)
w.wg.Wait()
}
type singleThreadedWorkGroup struct {
done atomic.Bool
fnsMu sync.Mutex
fns []func()
}
var _ WorkGroup = (*singleThreadedWorkGroup)(nil)
func (w *singleThreadedWorkGroup) Queue(fn func()) {
if w.done.Load() {
panic("Queue called after RunAndWait returned")
}
w.fnsMu.Lock()
defer w.fnsMu.Unlock()
w.fns = append(w.fns, fn)
}
func (w *singleThreadedWorkGroup) RunAndWait() {
defer w.done.Store(true)
for {
fn := w.pop()
if fn == nil {
return
}
fn()
}
}
func (w *singleThreadedWorkGroup) pop() func() {
w.fnsMu.Lock()
defer w.fnsMu.Unlock()
if len(w.fns) == 0 {
return nil
}
end := len(w.fns) - 1
fn := w.fns[end]
w.fns[end] = nil // Allow GC
w.fns = w.fns[:end]
return fn
}
// ThrottleGroup is like errgroup.Group but with global concurrency limiting via a semaphore.
type ThrottleGroup struct {
semaphore chan struct{}
group *errgroup.Group
}
// NewThrottleGroup creates a new ThrottleGroup with the given context and semaphore for concurrency limiting.
func NewThrottleGroup(ctx context.Context, semaphore chan struct{}) *ThrottleGroup {
g, _ := errgroup.WithContext(ctx)
return &ThrottleGroup{
semaphore: semaphore,
group: g,
}
}
// Go runs the given function in a new goroutine, but first acquires a slot from the semaphore.
// The semaphore slot is released when the function completes.
func (tg *ThrottleGroup) Go(fn func() error) {
tg.group.Go(func() error {
// Acquire semaphore slot - this will block until a slot is available
tg.semaphore <- struct{}{}
defer func() {
// Release semaphore slot when done
<-tg.semaphore
}()
return fn()
})
}
// Wait waits for all goroutines to complete and returns the first error encountered, if any.
func (tg *ThrottleGroup) Wait() error {
return tg.group.Wait()
}

View File

@ -0,0 +1,198 @@
//go:build !release
package debug
import (
"fmt"
"reflect"
)
func Assert(expression bool, message ...string) {
if !expression {
var msg string
if len(message) > 0 {
msg = "False expression: " + message[0]
} else {
msg = "False expression."
}
Fail(msg)
}
}
func isNil[T any](value T) bool {
v := reflect.ValueOf(value)
switch v.Kind() {
case reflect.Pointer, reflect.UnsafePointer, reflect.Interface, reflect.Slice, reflect.Map, reflect.Chan, reflect.Func:
{
return v.IsNil()
}
default:
{
return false
}
}
}
func AssertNil(value any, message ...string) {
if value != nil && !isNil(value) {
var msg string
if len(message) > 0 {
msg = "Nil expression: " + message[0]
} else {
msg = "Nil expression."
}
Fail(msg)
}
}
func AssertEqual(a fmt.Stringer, b fmt.Stringer, message ...string) {
if a != b {
var msg string
if len(message) == 0 {
msg = ""
} else {
msg = message[0]
}
Fail(fmt.Sprintf("Expected %s == %s. %s", a.String(), b.String(), msg))
}
}
func AssertLessThan(a int, b int, message ...string) {
if a >= b {
var msg string
if len(message) == 0 {
msg = ""
} else {
msg = message[0]
}
Fail(fmt.Sprintf("Expected %d < %d. %s", a, b, msg))
}
}
func AssertLessThanOrEqual(a int, b int, message ...string) {
if a > b {
var msg string
if len(message) == 0 {
msg = ""
} else {
msg = message[0]
}
Fail(fmt.Sprintf("Expected %d <= %d. %s", a, b, msg))
}
}
func AssertGreaterThan(a int, b int, message ...string) {
if a <= b {
var msg string
if len(message) == 0 {
msg = ""
} else {
msg = message[0]
}
Fail(fmt.Sprintf("Expected %d > %d. %s", a, b, msg))
}
}
func AssertGreaterThanOrEqual(a int, b int, message ...string) {
if a < b {
var msg string
if len(message) == 0 {
msg = ""
} else {
msg = message[0]
}
Fail(fmt.Sprintf("Expected %d >= %d. %s", a, b, msg))
}
}
func AssertIsDefined(value any, message ...string) {
if value == nil || isNil(value) { // handle all `nil` interfaces
var msg string
if len(message) == 0 {
msg = ""
} else {
msg = message[0]
}
Fail(msg)
}
}
func CheckDefined[T any](value T, message ...string) T {
AssertIsDefined(value, message...)
return value
}
func AssertEachIsDefined[TElem any](value []TElem, message ...string) {
for _, elem := range value {
AssertIsDefined(elem, message...)
}
}
func CheckEachIsDefined[TElem any](value []TElem, message ...string) []TElem {
AssertEachIsDefined(value, message...)
return value
}
var unexpectedNode []string = []string{"Unexpected node."}
func AssertEachNode[TElem any](nodes []TElem, test func(elem TElem) bool, message ...string) {
if len(message) == 0 {
message = unexpectedNode
}
for _, elem := range nodes {
AssertNode(elem, test, message...)
}
}
func AssertNode[TElem any](node TElem, test func(elem TElem) bool, message ...string) {
if len(message) == 0 {
message = unexpectedNode
}
AssertIsDefined(node, message...)
if test != nil {
Assert(test(node), message...)
}
}
func AssertNotNode[TElem any](node TElem, test func(elem TElem) bool, message ...string) {
if isNil(node) {
return
}
if test == nil {
return
}
if len(message) == 0 {
message = unexpectedNode
}
Assert(!test(node), message...)
}
func AssertOptionalNode[TElem any](node TElem, test func(elem TElem) bool, message ...string) {
if isNil(node) {
return
}
if test == nil {
return
}
if len(message) == 0 {
message = unexpectedNode
}
Assert(test(node), message...)
}
func AssertOptionalToken[TElem interface{ KindValue() int16 }](node TElem, kind int16, message ...string) {
if isNil(node) {
return
}
if len(message) == 0 {
message = unexpectedNode
}
Assert(node.KindValue() == kind, message...)
}
func AssertMissingNode[TElem any](node TElem, message ...string) {
if len(message) == 0 {
message = unexpectedNode
}
Assert(isNil(node), message...)
}

View File

@ -0,0 +1,26 @@
//go:build release
package debug
import (
"fmt"
)
func Assert(expression bool, message ...string) {}
func AssertNil(value any, message ...string) {}
func AssertEqual(a fmt.Stringer, b fmt.Stringer, msg ...string) {}
func AssertLessThan(a int, b int, message ...string) {}
func AssertLessThanOrEqual(a int, b int, message ...string) {}
func AssertGreaterThan(a int, b int, message ...string) {}
func AssertGreaterThanOrEqual(a int, b int, message ...string) {}
func AssertIsDefined(value any, message ...string) {}
func CheckDefined[T any](value T, message ...string) T { return value }
func AssertEachIsDefined[TElem any](value []TElem, message ...string) {}
func CheckEachIsDefined[TElem any](value []TElem, message ...string) []TElem { return value }
func AssertEachNode[TElem any](nodes []TElem, test func(elem TElem) bool, message ...string) {}
func AssertNode[TElem any](node TElem, test func(elem TElem) bool, message ...string) {}
func AssertNotNode[TElem any](node TElem, test func(elem TElem) bool, message ...string) {}
func AssertOptionalNode[TElem any](node TElem, test func(elem TElem) bool, message ...string) {}
func AssertOptionalToken[TElem interface{ KindValue() int }](node TElem, kind int, message ...string) {
}
func AssertMissingNode[TElem any](node TElem, message ...string) {}

View File

@ -0,0 +1,43 @@
package debug
import (
"fmt"
)
func Fail(reason string) {
if len(reason) == 0 {
reason = "Debug failure."
} else {
reason = "Debug failure. " + reason
}
// runtime.Breakpoint()
panic(reason)
}
func FailBadSyntaxKind(node interface{ KindString() string }, message ...string) {
var msg string
if len(message) == 0 {
msg = "Unexpected node."
} else {
msg = message[0]
}
Fail(fmt.Sprintf("%s\r\nNode %s was unexpected.", msg, node.KindString()))
}
func AssertNever(member any, message ...string) {
var msg string
if len(message) == 0 {
msg = "Illegal value:"
} else {
msg = message[0]
}
var detail string
if member, ok := member.(interface{ KindString() string }); ok {
detail = member.KindString()
} else if member, ok := member.(fmt.Stringer); ok {
detail = member.String()
} else {
detail = fmt.Sprintf("%v", member)
}
Fail(fmt.Sprintf("%s %s", msg, detail))
}

View File

@ -0,0 +1,63 @@
// Package diagnostics contains generated localizable diagnostic messages.
package diagnostics
import "efprojects.com/kitten-ipc/kitcom/internal/tsgo/stringutil"
//go:generate go run generate.go -output ./diagnostics_generated.go
//go:generate go tool golang.org/x/tools/cmd/stringer -type=Category -output=stringer_generated.go
//go:generate go tool mvdan.cc/gofumpt -w diagnostics_generated.go stringer_generated.go
type Category int32
const (
CategoryWarning Category = iota
CategoryError
CategorySuggestion
CategoryMessage
)
func (category Category) Name() string {
switch category {
case CategoryWarning:
return "warning"
case CategoryError:
return "error"
case CategorySuggestion:
return "suggestion"
case CategoryMessage:
return "message"
}
panic("Unhandled diagnostic category")
}
type Message struct {
code int32
category Category
key string
text string
reportsUnnecessary bool
elidedInCompatibilityPyramid bool
reportsDeprecated bool
}
func (m *Message) Code() int32 { return m.code }
func (m *Message) Category() Category { return m.category }
func (m *Message) Key() string { return m.key }
func (m *Message) Message() string { return m.text }
func (m *Message) ReportsUnnecessary() bool { return m.reportsUnnecessary }
func (m *Message) ElidedInCompatibilityPyramid() bool { return m.elidedInCompatibilityPyramid }
func (m *Message) ReportsDeprecated() bool { return m.reportsDeprecated }
func (m *Message) Format(args ...any) string {
text := m.Message()
if len(args) != 0 {
text = stringutil.Format(text, args)
}
return text
}
func FormatMessage(m *Message, args ...any) *Message {
result := *m
result.text = stringutil.Format(m.text, args)
return &result
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,42 @@
{
"Do not print diagnostics.": {
"category": "Message",
"code": 100000
},
"Run in single threaded mode.": {
"category": "Message",
"code": 100001
},
"Generate pprof CPU/memory profiles to the given directory.": {
"category": "Message",
"code": 100002
},
"Non-relative paths are not allowed. Did you forget a leading './'?": {
"category": "Error",
"code": 5090
},
"A JSDoc '@type' tag on a function must have a signature with the correct number of arguments.": {
"category": "Error",
"code": 8030
},
"A JSDoc '@type' tag may not occur with a '@param' or '@returns' tag.": {
"category": "Error",
"code": 8040
},
"Failed to delete file '{0}'.": {
"category": "Message",
"code": 6353
},
"Project '{0}' is out of date because config file does not exist.": {
"category": "Message",
"code": 6401
},
"Project '{0}' is out of date because input '{1}' does not exist.": {
"category": "Message",
"code": 6420
},
"Project '{0}' is out of date because it has errors.": {
"category": "Message",
"code": 6423
}
}

View File

@ -0,0 +1,183 @@
//go:build ignore
package main
import (
"bytes"
"cmp"
"flag"
"fmt"
"go/format"
"go/token"
"log"
"maps"
"os"
"path/filepath"
"regexp"
"runtime"
"slices"
"strconv"
"strings"
"unicode"
"github.com/go-json-experiment/json"
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/repo"
)
type diagnosticMessage struct {
Category string `json:"category"`
Code int `json:"code"`
ReportsUnnecessary bool `json:"reportsUnnecessary"`
ReportsDeprecated bool `json:"reportsDeprecated"`
// spelling error here is [sic] in Strada
ElidedInCompatibilityPyramid bool `json:"elidedInCompatabilityPyramid"`
key string
}
func main() {
log.SetFlags(log.LstdFlags | log.Lshortfile)
output := flag.String("output", "", "path to the output diagnostics_generated.go file")
flag.Parse()
if *output == "" {
flag.Usage()
return
}
rawDiagnosticMessages := readRawMessages(filepath.Join(repo.TypeScriptSubmodulePath, "src", "compiler", "diagnosticMessages.json"))
_, filename, _, ok := runtime.Caller(0)
if !ok {
panic("could not get current filename")
}
filename = filepath.FromSlash(filename) // runtime.Caller always returns forward slashes; https://go.dev/issues/3335, https://go.dev/cl/603275
rawExtraMessages := readRawMessages(filepath.Join(filepath.Dir(filename), "extraDiagnosticMessages.json"))
maps.Copy(rawDiagnosticMessages, rawExtraMessages)
diagnosticMessages := slices.Collect(maps.Values(rawDiagnosticMessages))
slices.SortFunc(diagnosticMessages, func(a *diagnosticMessage, b *diagnosticMessage) int {
return cmp.Compare(a.Code, b.Code)
})
var buf bytes.Buffer
buf.WriteString("// Code generated by generate.go; DO NOT EDIT.\n")
buf.WriteString("\n")
buf.WriteString("package diagnostics\n")
for _, m := range diagnosticMessages {
varName, key := convertPropertyName(m.key, m.Code)
fmt.Fprintf(&buf, "var %s = &Message{code: %d, category: Category%s, key: %q, text: %q", varName, m.Code, m.Category, key, m.key)
if m.ReportsUnnecessary {
buf.WriteString(`, reportsUnnecessary: true`)
}
if m.ElidedInCompatibilityPyramid {
buf.WriteString(`, elidedInCompatibilityPyramid: true`)
}
if m.ReportsDeprecated {
buf.WriteString(`, reportsDeprecated: true`)
}
buf.WriteString("}\n\n")
}
formatted, err := format.Source(buf.Bytes())
if err != nil {
log.Fatalf("failed to format output: %v", err)
return
}
if err := os.WriteFile(*output, formatted, 0o666); err != nil {
log.Fatalf("failed to write output: %v", err)
return
}
}
func readRawMessages(p string) map[int]*diagnosticMessage {
file, err := os.Open(p)
if err != nil {
log.Fatalf("failed to open file: %v", err)
return nil
}
defer file.Close()
var rawMessages map[string]*diagnosticMessage
if err := json.UnmarshalRead(file, &rawMessages); err != nil {
log.Fatalf("failed to decode file: %v", err)
return nil
}
codeToMessage := make(map[int]*diagnosticMessage, len(rawMessages))
for k, m := range rawMessages {
m.key = k
codeToMessage[m.Code] = m
}
return codeToMessage
}
var (
multipleUnderscoreRegexp = regexp.MustCompile(`_+`)
leadingUnderscoreUnlessDigitRegexp = regexp.MustCompile(`^_+(\D)`)
trailingUnderscoreRegexp = regexp.MustCompile(`_$`)
)
func convertPropertyName(origName string, code int) (varName string, key string) {
var b strings.Builder
b.Grow(len(origName))
for _, r := range origName {
switch r {
case '*':
b.WriteString("_Asterisk")
case '/':
b.WriteString("_Slash")
case ':':
b.WriteString("_Colon")
default:
if !unicode.IsLetter(r) && !unicode.IsDigit(r) {
b.WriteRune('_')
} else {
b.WriteRune(r)
}
}
}
varName = b.String()
// get rid of all multi-underscores
varName = multipleUnderscoreRegexp.ReplaceAllString(varName, "_")
// remove any leading underscore, unless it is followed by a number.
varName = leadingUnderscoreUnlessDigitRegexp.ReplaceAllString(varName, "$1")
// get rid of all trailing underscores.
varName = trailingUnderscoreRegexp.ReplaceAllString(varName, "")
key = varName
if len(key) > 100 {
key = key[:100]
}
key = key + "_" + strconv.Itoa(code)
if !token.IsExported(varName) {
var b strings.Builder
b.Grow(len(varName) + 2)
if varName[0] == '_' {
b.WriteString("X")
} else {
b.WriteString("X_")
}
b.WriteString(varName)
varName = b.String()
}
if !token.IsIdentifier(varName) || !token.IsExported(varName) {
log.Fatalf("failed to convert property name to exported identifier: %q", origName)
}
return varName, key
}

View File

@ -0,0 +1,26 @@
// Code generated by "stringer -type=Category -output=stringer_generated.go"; DO NOT EDIT.
package diagnostics
import "strconv"
func _() {
// An "invalid array index" compiler error signifies that the constant values have changed.
// Re-run the stringer command to generate them again.
var x [1]struct{}
_ = x[CategoryWarning-0]
_ = x[CategoryError-1]
_ = x[CategorySuggestion-2]
_ = x[CategoryMessage-3]
}
const _Category_name = "CategoryWarningCategoryErrorCategorySuggestionCategoryMessage"
var _Category_index = [...]uint8{0, 15, 28, 46, 61}
func (i Category) String() string {
if i < 0 || i >= Category(len(_Category_index)-1) {
return "Category(" + strconv.FormatInt(int64(i), 10) + ")"
}
return _Category_name[_Category_index[i]:_Category_index[i+1]]
}

View File

@ -0,0 +1,159 @@
// Package jsnum provides JS-like number handling.
package jsnum
import (
"math"
)
const (
MaxSafeInteger Number = 1<<53 - 1
MinSafeInteger Number = -MaxSafeInteger
)
// Number represents a JS-like number.
//
// All operations that can be performed directly on this type
// (e.g., conversion, arithmetic, etc.) behave as they would in JavaScript,
// but any other operation should use this type's methods,
// not the "math" package and conversions.
type Number float64
func NaN() Number {
return Number(math.NaN())
}
func (n Number) IsNaN() bool {
return math.IsNaN(float64(n))
}
func Inf(sign int) Number {
return Number(math.Inf(sign))
}
func (n Number) IsInf() bool {
return math.IsInf(float64(n), 0)
}
func isNonFinite(x float64) bool {
// This is equivalent to checking `math.IsNaN(x) || math.IsInf(x, 0)` in one operation.
const mask = 0x7FF0000000000000
return math.Float64bits(x)&mask == mask
}
// https://tc39.es/ecma262/2024/multipage/abstract-operations.html#sec-touint32
func (x Number) toUint32() uint32 {
// The only difference between ToUint32 and ToInt32 is the interpretation of the bits.
return uint32(x.toInt32())
}
// https://tc39.es/ecma262/2024/multipage/abstract-operations.html#sec-toint32
func (n Number) toInt32() int32 {
x := float64(n)
// Fast path: if the number is in the range (-2^31, 2^32), i.e. an SMI,
// then we don't need to do any special mapping.
if smi := int32(x); float64(smi) == x {
return smi
}
// 2. If number is not finite or number is either +0𝔽 or -0𝔽, return +0𝔽.
// Zero was covered by the test above.
if isNonFinite(x) {
return 0
}
// Let int be truncate((number)).
x = math.Trunc(x)
// Let int32bit be int modulo 2**32.
x = math.Mod(x, 1<<32)
// If int32bit ≥ 2**31, return 𝔽(int32bit - 2**32); otherwise return 𝔽(int32bit).
return int32(int64(x))
}
func (x Number) toShiftCount() uint32 {
return x.toUint32() & 31
}
// https://tc39.es/ecma262/2024/multipage/ecmascript-data-types-and-values.html#sec-numeric-types-number-signedRightShift
func (x Number) SignedRightShift(y Number) Number {
return Number(x.toInt32() >> y.toShiftCount())
}
// https://tc39.es/ecma262/2024/multipage/ecmascript-data-types-and-values.html#sec-numeric-types-number-unsignedRightShift
func (x Number) UnsignedRightShift(y Number) Number {
return Number(x.toUint32() >> y.toShiftCount())
}
// https://tc39.es/ecma262/2024/multipage/ecmascript-data-types-and-values.html#sec-numeric-types-number-leftShift
func (x Number) LeftShift(y Number) Number {
return Number(x.toInt32() << y.toShiftCount())
}
// https://tc39.es/ecma262/2024/multipage/ecmascript-data-types-and-values.html#sec-numeric-types-number-bitwiseNOT
func (x Number) BitwiseNOT() Number {
return Number(^x.toInt32())
}
// The below are implemented by https://tc39.es/ecma262/2024/multipage/ecmascript-data-types-and-values.html#sec-numberbitwiseop.
// https://tc39.es/ecma262/2024/multipage/ecmascript-data-types-and-values.html#sec-numeric-types-number-bitwiseOR
func (x Number) BitwiseOR(y Number) Number {
return Number(x.toInt32() | y.toInt32())
}
// https://tc39.es/ecma262/2024/multipage/ecmascript-data-types-and-values.html#sec-numeric-types-number-bitwiseAND
func (x Number) BitwiseAND(y Number) Number {
return Number(x.toInt32() & y.toInt32())
}
// https://tc39.es/ecma262/2024/multipage/ecmascript-data-types-and-values.html#sec-numeric-types-number-bitwiseXOR
func (x Number) BitwiseXOR(y Number) Number {
return Number(x.toInt32() ^ y.toInt32())
}
func (x Number) trunc() Number {
return Number(math.Trunc(float64(x)))
}
func (x Number) Floor() Number {
return Number(math.Floor(float64(x)))
}
func (x Number) Abs() Number {
return Number(math.Abs(float64(x)))
}
var negativeZero = Number(math.Copysign(0, -1))
// https://tc39.es/ecma262/2024/multipage/ecmascript-data-types-and-values.html#sec-numeric-types-number-remainder
func (n Number) Remainder(d Number) Number {
switch {
case n.IsNaN() || d.IsNaN():
return NaN()
case n.IsInf():
return NaN()
case d.IsInf():
return n
case d == 0:
return NaN()
case n == 0:
return n
}
r := n - d*(n/d).trunc()
if r == 0 && n < 0 {
return negativeZero
}
return r
}
// https://tc39.es/ecma262/2024/multipage/ecmascript-data-types-and-values.html#sec-numeric-types-number-exponentiate
func (base Number) Exponentiate(exponent Number) Number {
switch {
case (base == 1 || base == -1) && exponent.IsInf():
return NaN()
case base == 1 && exponent.IsNaN():
return NaN()
}
return Number(math.Pow(float64(base), float64(exponent)))
}

View File

@ -0,0 +1,66 @@
package jsnum
import (
"fmt"
"math/big"
"strings"
)
// PseudoBigInt represents a JS-like bigint. The zero state of the struct represents the value 0.
type PseudoBigInt struct {
Negative bool // true if the value is a non-zero negative number.
Base10Value string // The absolute value in base 10 with no leading zeros. The value zero is represented as an empty string.
}
func NewPseudoBigInt(value string, negative bool) PseudoBigInt {
value = strings.TrimLeft(value, "0")
return PseudoBigInt{Negative: negative && len(value) != 0, Base10Value: value}
}
func (value PseudoBigInt) String() string {
if len(value.Base10Value) == 0 {
return "0"
}
if value.Negative {
return "-" + value.Base10Value
}
return value.Base10Value
}
func (value PseudoBigInt) Sign() int {
if len(value.Base10Value) == 0 {
return 0
}
if value.Negative {
return -1
}
return 1
}
func ParseValidBigInt(text string) PseudoBigInt {
text, negative := strings.CutPrefix(text, "-")
return NewPseudoBigInt(ParsePseudoBigInt(text), negative)
}
func ParsePseudoBigInt(stringValue string) string {
stringValue = strings.TrimSuffix(stringValue, "n")
var b1 byte
if len(stringValue) > 1 {
b1 = stringValue[1]
}
switch b1 {
case 'b', 'B', 'o', 'O', 'x', 'X':
// Not decimal.
default:
stringValue = strings.TrimLeft(stringValue, "0")
if stringValue == "" {
return "0"
}
return stringValue
}
bi, ok := new(big.Int).SetString(stringValue, 0)
if !ok {
panic(fmt.Sprintf("Failed to parse big int: %q", stringValue))
}
return bi.String() // !!!
}

View File

@ -0,0 +1,341 @@
package jsnum
import (
"errors"
"math"
"math/big"
"strconv"
"strings"
"unicode"
"unicode/utf8"
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/stringutil"
"github.com/go-json-experiment/json"
)
// https://tc39.es/ecma262/2024/multipage/ecmascript-data-types-and-values.html#sec-numeric-types-number-tostring
func (n Number) String() string {
switch {
case n.IsNaN():
return "NaN"
case n.IsInf():
if n < 0 {
return "-Infinity"
}
return "Infinity"
}
// Fast path: for safe integers, directly convert to string.
if MinSafeInteger <= n && n <= MaxSafeInteger {
if i := int64(n); float64(i) == float64(n) {
return strconv.FormatInt(i, 10)
}
}
// Otherwise, the Go json package handles this correctly.
b, _ := json.Marshal(float64(n))
return string(b)
}
// https://tc39.es/ecma262/2024/multipage/abstract-operations.html#sec-stringtonumber
func FromString(s string) Number {
// Implementing StringToNumber exactly as written in the spec involves
// writing a parser, along with the conversion of the parsed AST into the
// actual value.
//
// We've already implemented a number parser in the scanner, but we can't
// import it here. We also do not have the conversion implemented since we
// previously just wrote `+literal` and let the runtime handle it.
//
// The strategy below is to instead break the number apart and fix it up
// such that Go's own parsing functionality can handle it. This won't be
// the fastest method, but it saves us from writing the full parser and
// conversion logic.
s = strings.TrimFunc(s, isStrWhiteSpace)
switch s {
case "":
return 0
case "Infinity", "+Infinity":
return Inf(1)
case "-Infinity":
return Inf(-1)
}
for _, r := range s {
if !isNumberRune(r) {
return NaN()
}
}
if n, ok := tryParseInt(s); ok {
return n
}
// Cut this off first so we can ensure -0 is returned as -0.
s, negative := strings.CutPrefix(s, "-")
if !negative {
s, _ = strings.CutPrefix(s, "+")
}
if first, _ := utf8.DecodeRuneInString(s); !stringutil.IsDigit(first) && first != '.' {
return NaN()
}
f := parseFloatString(s)
if math.IsNaN(f) {
return NaN()
}
sign := 1.0
if negative {
sign = -1.0
}
return Number(math.Copysign(f, sign))
}
func isStrWhiteSpace(r rune) bool {
// This is different than stringutil.IsWhiteSpaceLike.
// https://tc39.es/ecma262/2024/multipage/ecmascript-language-lexical-grammar.html#prod-LineTerminator
// https://tc39.es/ecma262/2024/multipage/ecmascript-language-lexical-grammar.html#prod-WhiteSpace
switch r {
// LineTerminator
case '\n', '\r', 0x2028, 0x2029:
return true
// WhiteSpace
case '\t', '\v', '\f', 0xFEFF:
return true
}
// WhiteSpace
return unicode.Is(unicode.Zs, r)
}
var errUnknownPrefix = errors.New("unknown number prefix")
func tryParseInt(s string) (Number, bool) {
var i int64
var err error
var hasIntResult bool
if len(s) > 2 {
prefix, rest := s[:2], s[2:]
switch prefix {
case "0b", "0B":
if !isAllBinaryDigits(rest) {
return NaN(), true
}
i, err = strconv.ParseInt(rest, 2, 64)
hasIntResult = true
case "0o", "0O":
if !isAllOctalDigits(rest) {
return NaN(), true
}
i, err = strconv.ParseInt(rest, 8, 64)
hasIntResult = true
case "0x", "0X":
if !isAllHexDigits(rest) {
return NaN(), true
}
i, err = strconv.ParseInt(rest, 16, 64)
hasIntResult = true
}
}
if !hasIntResult {
// StringToNumber does not parse leading zeros as octal.
s = trimLeadingZeros(s)
if !isAllDigits(s) {
return 0, false
}
i, err = strconv.ParseInt(s, 10, 64)
hasIntResult = true
}
if hasIntResult && err == nil {
return Number(i), true
}
// Using this to parse large integers.
bi, ok := new(big.Int).SetString(s, 0)
if !ok {
return NaN(), true
}
f, _ := bi.Float64()
return Number(f), true
}
func parseFloatString(s string) float64 {
var hasDot, hasExp bool
// <a>
// <a>.<b>
// <a>.<b>e<c>
// <a>e<c>
var a, b, c, rest string
a, rest, hasDot = strings.Cut(s, ".")
if hasDot {
// <a>.<b>
// <a>.<b>e<c>
b, c, hasExp = cutAny(rest, "eE")
} else {
// <a>
// <a>e<c>
a, c, hasExp = cutAny(s, "eE")
}
var sb strings.Builder
sb.Grow(len(a) + len(b) + len(c) + 3)
if a == "" {
if hasDot && b == "" {
return math.NaN()
}
if hasExp && c == "" {
return math.NaN()
}
sb.WriteString("0")
} else {
a = trimLeadingZeros(a)
if !isAllDigits(a) {
return math.NaN()
}
sb.WriteString(a)
}
if hasDot {
sb.WriteString(".")
if b == "" {
sb.WriteString("0")
} else {
b = trimTrailingZeros(b)
if !isAllDigits(b) {
return math.NaN()
}
sb.WriteString(b)
}
}
if hasExp {
sb.WriteString("e")
c, negative := strings.CutPrefix(c, "-")
if negative {
sb.WriteString("-")
} else {
c, _ = strings.CutPrefix(c, "+")
}
c = trimLeadingZeros(c)
if !isAllDigits(c) {
return math.NaN()
}
sb.WriteString(c)
}
return stringToFloat64(sb.String())
}
func cutAny(s string, cutset string) (before, after string, found bool) {
if i := strings.IndexAny(s, cutset); i >= 0 {
before = s[:i]
afterAndFound := s[i:]
_, size := utf8.DecodeRuneInString(afterAndFound)
after = afterAndFound[size:]
return before, after, true
}
return s, "", false
}
func trimLeadingZeros(s string) string {
if strings.HasPrefix(s, "0") {
s = strings.TrimLeft(s, "0")
if s == "" {
return "0"
}
}
return s
}
func trimTrailingZeros(s string) string {
if strings.HasSuffix(s, "0") {
s = strings.TrimRight(s, "0")
if s == "" {
return "0"
}
}
return s
}
func stringToFloat64(s string) float64 {
if f, err := strconv.ParseFloat(s, 64); err == nil {
return f
} else {
if errors.Is(err, strconv.ErrRange) {
return f
}
}
return math.NaN()
}
func isAllDigits(s string) bool {
for _, r := range s {
if !stringutil.IsDigit(r) {
return false
}
}
return true
}
func isAllBinaryDigits(s string) bool {
for _, r := range s {
if r != '0' && r != '1' {
return false
}
}
return true
}
func isAllOctalDigits(s string) bool {
for _, r := range s {
if !stringutil.IsOctalDigit(r) {
return false
}
}
return true
}
func isAllHexDigits(s string) bool {
for _, r := range s {
if !stringutil.IsHexDigit(r) {
return false
}
}
return true
}
func isNumberRune(r rune) bool {
if stringutil.IsDigit(r) {
return true
}
if 'a' <= r && r <= 'f' {
return true
}
if 'A' <= r && r <= 'F' {
return true
}
switch r {
case '.', '-', '+', 'x', 'X', 'o', 'O':
return true
}
return false
}

View File

@ -0,0 +1,24 @@
package jsonutil
import (
"io"
"github.com/go-json-experiment/json"
"github.com/go-json-experiment/json/jsontext"
)
func MarshalIndent(in any, prefix, indent string) (out []byte, err error) {
if prefix == "" && indent == "" {
// WithIndentPrefix and WithIndent imply multiline output, so skip them.
return json.Marshal(in)
}
return json.Marshal(in, jsontext.WithIndentPrefix(prefix), jsontext.WithIndent(indent))
}
func MarshalIndentWrite(out io.Writer, in any, prefix, indent string) (err error) {
if prefix == "" && indent == "" {
// WithIndentPrefix and WithIndent imply multiline output, so skip them.
return json.MarshalWrite(out, in)
}
return json.MarshalWrite(out, in, jsontext.WithIndentPrefix(prefix), jsontext.WithIndent(indent))
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,74 @@
package parser
import (
"strings"
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/ast"
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/core"
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/tspath"
)
func collectExternalModuleReferences(file *ast.SourceFile) {
for _, node := range file.Statements.Nodes {
collectModuleReferences(file, node, false /*inAmbientModule*/)
}
if file.Flags&ast.NodeFlagsPossiblyContainsDynamicImport != 0 || ast.IsInJSFile(file.AsNode()) {
ast.ForEachDynamicImportOrRequireCall(file /*includeTypeSpaceImports*/, true /*requireStringLiteralLikeArgument*/, true, func(node *ast.Node, moduleSpecifier *ast.Expression) bool {
ast.SetImportsOfSourceFile(file, append(file.Imports(), moduleSpecifier))
return false
})
}
}
func collectModuleReferences(file *ast.SourceFile, node *ast.Statement, inAmbientModule bool) {
if ast.IsAnyImportOrReExport(node) {
moduleNameExpr := ast.GetExternalModuleName(node)
// TypeScript 1.0 spec (April 2014): 12.1.6
// An ExternalImportDeclaration in an AmbientExternalModuleDeclaration may reference other external modules
// only through top - level external module names. Relative external module names are not permitted.
if moduleNameExpr != nil && ast.IsStringLiteral(moduleNameExpr) {
moduleName := moduleNameExpr.AsStringLiteral().Text
if moduleName != "" && (!inAmbientModule || !tspath.IsExternalModuleNameRelative(moduleName)) {
ast.SetImportsOfSourceFile(file, append(file.Imports(), moduleNameExpr))
// !!! removed `&& p.currentNodeModulesDepth == 0`
if file.UsesUriStyleNodeCoreModules != core.TSTrue && !file.IsDeclarationFile {
if strings.HasPrefix(moduleName, "node:") && !core.ExclusivelyPrefixedNodeCoreModules[moduleName] {
// Presence of `node:` prefix takes precedence over unprefixed node core modules
file.UsesUriStyleNodeCoreModules = core.TSTrue
} else if file.UsesUriStyleNodeCoreModules == core.TSUnknown && core.UnprefixedNodeCoreModules[moduleName] {
// Avoid `unprefixedNodeCoreModules.has` for every import
file.UsesUriStyleNodeCoreModules = core.TSFalse
}
}
}
}
return
}
if ast.IsModuleDeclaration(node) && ast.IsAmbientModule(node) && (inAmbientModule || ast.HasSyntacticModifier(node, ast.ModifierFlagsAmbient) || file.IsDeclarationFile) {
nameText := node.AsModuleDeclaration().Name().Text()
// Ambient module declarations can be interpreted as augmentations for some existing external modules.
// This will happen in two cases:
// - if current file is external module then module augmentation is a ambient module declaration defined in the top level scope
// - if current file is not external module then module augmentation is an ambient module declaration with non-relative module name
// immediately nested in top level ambient module declaration .
if ast.IsExternalModule(file) || (inAmbientModule && !tspath.IsExternalModuleNameRelative(nameText)) {
file.ModuleAugmentations = append(file.ModuleAugmentations, node.AsModuleDeclaration().Name())
} else if !inAmbientModule {
if file.IsDeclarationFile {
// for global .d.ts files record name of ambient module
file.AmbientModuleNames = append(file.AmbientModuleNames, nameText)
}
// An AmbientExternalModuleDeclaration declares an external module.
// This type of declaration is permitted only in the global module.
// The StringLiteral must specify a top - level external module name.
// Relative external module names are not permitted
// NOTE: body of ambient module is always a module block, if it exists
if node.AsModuleDeclaration().Body != nil {
for _, statement := range node.AsModuleDeclaration().Body.AsModuleBlock().Statements.Nodes {
collectModuleReferences(file, statement, true /*inAmbientModule*/)
}
}
}
}
}

View File

@ -0,0 +1,619 @@
package parser
import (
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/ast"
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/core"
)
func (p *Parser) finishReparsedNode(node *ast.Node, locationNode *ast.Node) {
node.Flags = p.contextFlags | ast.NodeFlagsReparsed
node.Loc = locationNode.Loc
p.overrideParentInImmediateChildren(node)
}
func (p *Parser) finishMutatedNode(node *ast.Node) {
p.overrideParentInImmediateChildren(node)
}
func (p *Parser) reparseCommonJS(node *ast.Node, jsdoc []*ast.Node) {
if p.scriptKind != core.ScriptKindJS && p.scriptKind != core.ScriptKindJSX {
return
}
if node.Kind != ast.KindExpressionStatement || node.AsExpressionStatement().Expression.Kind != ast.KindBinaryExpression {
return
}
bin := node.AsExpressionStatement().Expression.AsBinaryExpression()
kind := ast.GetAssignmentDeclarationKind(bin)
var export *ast.Node
switch kind {
case ast.JSDeclarationKindModuleExports:
export = p.factory.NewJSExportAssignment(nil, p.factory.DeepCloneReparse(bin.Right))
case ast.JSDeclarationKindExportsProperty:
mod := p.factory.NewModifier(ast.KindExportKeyword)
mod.Flags = p.contextFlags | ast.NodeFlagsReparsed
mod.Loc = bin.Loc
// TODO: Name can sometimes be a string literal, so downstream code needs to handle this
export = p.factory.NewCommonJSExport(
p.newModifierList(bin.Loc, p.nodeSlicePool.NewSlice1(mod)),
p.factory.DeepCloneReparse(ast.GetElementOrPropertyAccessName(bin.Left)),
nil, /*typeNode*/
p.factory.DeepCloneReparse(bin.Right))
}
if export != nil {
p.reparseList = append(p.reparseList, export)
p.commonJSModuleIndicator = export
p.reparseTags(export, jsdoc)
p.finishReparsedNode(export, bin.AsNode())
}
}
// Hosted tags find a host and add their children to the correct location under the host.
// Unhosted tags add synthetic nodes to the reparse list.
func (p *Parser) reparseTags(parent *ast.Node, jsDoc []*ast.Node) {
for _, j := range jsDoc {
isLast := j == jsDoc[len(jsDoc)-1]
tags := j.AsJSDoc().Tags
if tags == nil {
continue
}
for _, tag := range tags.Nodes {
if parent.Kind != ast.KindCommonJSExport && parent.Kind != ast.KindJSExportAssignment {
p.reparseUnhosted(tag, parent, j)
}
if isLast {
p.reparseHosted(tag, parent, j)
}
}
}
}
func (p *Parser) reparseUnhosted(tag *ast.Node, parent *ast.Node, jsDoc *ast.Node) {
switch tag.Kind {
case ast.KindJSDocTypedefTag:
// !!! Don't mark typedefs as exported if they are not in a module
typeExpression := tag.AsJSDocTypedefTag().TypeExpression
if typeExpression == nil {
break
}
export := p.factory.NewModifier(ast.KindExportKeyword)
export.Loc = tag.Loc
export.Flags = p.contextFlags | ast.NodeFlagsReparsed
modifiers := p.newModifierList(export.Loc, p.nodeSlicePool.NewSlice1(export))
typeAlias := p.factory.NewJSTypeAliasDeclaration(modifiers, p.factory.DeepCloneReparse(tag.AsJSDocTypedefTag().Name()), nil, nil)
typeAlias.AsTypeAliasDeclaration().TypeParameters = p.gatherTypeParameters(jsDoc, tag)
var t *ast.Node
switch typeExpression.Kind {
case ast.KindJSDocTypeExpression:
t = p.factory.DeepCloneReparse(typeExpression.Type())
case ast.KindJSDocTypeLiteral:
t = p.reparseJSDocTypeLiteral(typeExpression)
default:
panic("typedef tag type expression should be a name reference or a type expression" + typeExpression.Kind.String())
}
typeAlias.AsTypeAliasDeclaration().Type = t
p.finishReparsedNode(typeAlias, tag)
p.reparseList = append(p.reparseList, typeAlias)
case ast.KindJSDocCallbackTag:
callbackTag := tag.AsJSDocCallbackTag()
if callbackTag.TypeExpression == nil {
break
}
export := p.factory.NewModifier(ast.KindExportKeyword)
export.Loc = tag.Loc
export.Flags = p.contextFlags | ast.NodeFlagsReparsed
modifiers := p.newModifierList(export.Loc, p.nodeSlicePool.NewSlice1(export))
functionType := p.reparseJSDocSignature(callbackTag.TypeExpression, tag, jsDoc, tag, nil)
typeAlias := p.factory.NewJSTypeAliasDeclaration(modifiers, p.factory.DeepCloneReparse(callbackTag.FullName), nil, functionType)
typeAlias.AsTypeAliasDeclaration().TypeParameters = p.gatherTypeParameters(jsDoc, tag)
p.finishReparsedNode(typeAlias, tag)
p.reparseList = append(p.reparseList, typeAlias)
case ast.KindJSDocImportTag:
importTag := tag.AsJSDocImportTag()
if importTag.ImportClause == nil {
break
}
importClause := p.factory.DeepCloneReparse(importTag.ImportClause)
importClause.AsImportClause().PhaseModifier = ast.KindTypeKeyword
importDeclaration := p.factory.NewJSImportDeclaration(
p.factory.DeepCloneReparseModifiers(importTag.Modifiers()),
importClause,
p.factory.DeepCloneReparse(importTag.ModuleSpecifier),
p.factory.DeepCloneReparse(importTag.Attributes),
)
p.finishReparsedNode(importDeclaration, tag)
p.reparseList = append(p.reparseList, importDeclaration)
case ast.KindJSDocOverloadTag:
if fun, ok := getFunctionLikeHost(parent); ok {
p.reparseList = append(p.reparseList, p.reparseJSDocSignature(tag.AsJSDocOverloadTag().TypeExpression, fun, jsDoc, tag, fun.Modifiers()))
}
}
}
func (p *Parser) reparseJSDocSignature(jsSignature *ast.Node, fun *ast.Node, jsDoc *ast.Node, tag *ast.Node, modifiers *ast.ModifierList) *ast.Node {
var signature *ast.Node
clonedModifiers := p.factory.DeepCloneReparseModifiers(modifiers)
switch fun.Kind {
case ast.KindFunctionDeclaration, ast.KindFunctionExpression, ast.KindArrowFunction:
signature = p.factory.NewFunctionDeclaration(clonedModifiers, nil, p.factory.DeepCloneReparse(fun.Name()), nil, nil, nil, nil, nil)
case ast.KindMethodDeclaration, ast.KindMethodSignature:
signature = p.factory.NewMethodDeclaration(clonedModifiers, nil, p.factory.DeepCloneReparse(fun.Name()), nil, nil, nil, nil, nil, nil)
case ast.KindConstructor:
signature = p.factory.NewConstructorDeclaration(clonedModifiers, nil, nil, nil, nil, nil)
case ast.KindJSDocCallbackTag:
signature = p.factory.NewFunctionTypeNode(nil, nil, nil)
default:
panic("Unexpected kind " + fun.Kind.String())
}
if tag.Kind != ast.KindJSDocCallbackTag {
signature.FunctionLikeData().TypeParameters = p.gatherTypeParameters(jsDoc, tag)
}
parameters := p.nodeSlicePool.NewSlice(0)
for _, param := range jsSignature.Parameters() {
var parameter *ast.Node
if param.Kind == ast.KindJSDocThisTag {
thisTag := param.AsJSDocThisTag()
thisIdent := p.factory.NewIdentifier("this")
thisIdent.Loc = thisTag.Loc
thisIdent.Flags = p.contextFlags | ast.NodeFlagsReparsed
parameter = p.factory.NewParameterDeclaration(nil, nil, thisIdent, nil, nil, nil)
if thisTag.TypeExpression != nil {
parameter.AsParameterDeclaration().Type = p.factory.DeepCloneReparse(thisTag.TypeExpression.Type())
}
} else {
jsparam := param.AsJSDocParameterOrPropertyTag()
var dotDotDotToken *ast.Node
var paramType *ast.TypeNode
if jsparam.TypeExpression != nil {
if jsparam.TypeExpression.Type().Kind == ast.KindJSDocVariadicType {
dotDotDotToken = p.factory.NewToken(ast.KindDotDotDotToken)
dotDotDotToken.Loc = jsparam.Loc
dotDotDotToken.Flags = p.contextFlags | ast.NodeFlagsReparsed
variadicType := jsparam.TypeExpression.Type().AsJSDocVariadicType()
paramType = p.reparseJSDocTypeLiteral(variadicType.Type)
} else {
paramType = p.reparseJSDocTypeLiteral(jsparam.TypeExpression.Type())
}
}
parameter = p.factory.NewParameterDeclaration(nil, dotDotDotToken, p.factory.DeepCloneReparse(jsparam.Name()), p.makeQuestionIfOptional(jsparam), paramType, nil)
}
p.finishReparsedNode(parameter, param)
parameters = append(parameters, parameter)
}
signature.FunctionLikeData().Parameters = p.newNodeList(jsSignature.AsJSDocSignature().Parameters.Loc, parameters)
if jsSignature.Type() != nil && jsSignature.Type().AsJSDocReturnTag().TypeExpression != nil {
signature.FunctionLikeData().Type = p.factory.DeepCloneReparse(jsSignature.Type().AsJSDocReturnTag().TypeExpression.Type())
}
loc := jsSignature
if tag.Kind == ast.KindJSDocOverloadTag {
loc = tag.AsJSDocOverloadTag().TagName
}
p.finishReparsedNode(signature, loc)
return signature
}
func (p *Parser) reparseJSDocTypeLiteral(t *ast.TypeNode) *ast.Node {
if t == nil {
return nil
}
if t.Kind == ast.KindJSDocTypeLiteral {
jstypeliteral := t.AsJSDocTypeLiteral()
isArrayType := jstypeliteral.IsArrayType
properties := p.nodeSlicePool.NewSlice(0)
for _, prop := range jstypeliteral.JSDocPropertyTags {
jsprop := prop.AsJSDocParameterOrPropertyTag()
name := prop.Name()
if name.Kind == ast.KindQualifiedName {
name = name.AsQualifiedName().Right
}
property := p.factory.NewPropertySignatureDeclaration(nil, p.factory.DeepCloneReparse(name), p.makeQuestionIfOptional(jsprop), nil, nil)
if jsprop.TypeExpression != nil {
property.AsPropertySignatureDeclaration().Type = p.reparseJSDocTypeLiteral(jsprop.TypeExpression.Type())
}
p.finishReparsedNode(property, prop)
properties = append(properties, property)
}
t = p.factory.NewTypeLiteralNode(p.newNodeList(jstypeliteral.Loc, properties))
if isArrayType {
p.finishReparsedNode(t, jstypeliteral.AsNode())
t = p.factory.NewArrayTypeNode(t)
}
p.finishReparsedNode(t, jstypeliteral.AsNode())
}
return p.factory.DeepCloneReparse(t)
}
func (p *Parser) gatherTypeParameters(j *ast.Node, tagWithTypeParameters *ast.Node) *ast.NodeList {
var typeParameters []*ast.Node
pos := -1
endPos := -1
firstTemplate := true
// type parameters only apply to the tag or node they occur before, so record a place to stop
start := 0
for i, other := range j.AsJSDoc().Tags.Nodes {
if other == tagWithTypeParameters {
break
}
if other.Kind == ast.KindJSDocTypedefTag || other.Kind == ast.KindJSDocCallbackTag || other.Kind == ast.KindJSDocOverloadTag {
start = i + 1
}
}
for i, tag := range j.AsJSDoc().Tags.Nodes {
if tag == tagWithTypeParameters {
break
}
if i < start || tag.Kind != ast.KindJSDocTemplateTag {
continue
}
if firstTemplate {
pos = tag.Pos()
firstTemplate = false
}
endPos = tag.End()
constraint := tag.AsJSDocTemplateTag().Constraint
firstTypeParameter := true
for _, tp := range tag.TypeParameters() {
var reparse *ast.Node
if constraint != nil && firstTypeParameter {
reparse = p.factory.NewTypeParameterDeclaration(
p.factory.DeepCloneReparseModifiers(tp.Modifiers()),
p.factory.DeepCloneReparse(tp.Name()),
p.factory.DeepCloneReparse(constraint.Type()),
p.factory.DeepCloneReparse(tp.AsTypeParameter().DefaultType),
)
p.finishReparsedNode(reparse, tp)
} else {
reparse = p.factory.DeepCloneReparse(tp)
}
if typeParameters == nil {
typeParameters = p.nodeSlicePool.NewSlice(0)
}
typeParameters = append(typeParameters, reparse)
firstTypeParameter = false
}
}
if len(typeParameters) == 0 {
return nil
} else {
return p.newNodeList(core.NewTextRange(pos, endPos), typeParameters)
}
}
func (p *Parser) reparseHosted(tag *ast.Node, parent *ast.Node, jsDoc *ast.Node) {
switch tag.Kind {
case ast.KindJSDocTypeTag:
switch parent.Kind {
case ast.KindVariableStatement:
if parent.AsVariableStatement().DeclarationList != nil {
for _, declaration := range parent.AsVariableStatement().DeclarationList.AsVariableDeclarationList().Declarations.Nodes {
if declaration.Type() == nil && tag.AsJSDocTypeTag().TypeExpression != nil {
declaration.AsMutable().SetType(p.factory.DeepCloneReparse(tag.AsJSDocTypeTag().TypeExpression.Type()))
p.finishMutatedNode(declaration)
return
}
}
}
case ast.KindVariableDeclaration,
ast.KindCommonJSExport, ast.KindExportAssignment, ast.KindJSExportAssignment,
ast.KindPropertyDeclaration, ast.KindPropertyAssignment, ast.KindShorthandPropertyAssignment:
if parent.Type() == nil && tag.AsJSDocTypeTag().TypeExpression != nil {
parent.AsMutable().SetType(p.factory.DeepCloneReparse(tag.AsJSDocTypeTag().TypeExpression.Type()))
p.finishMutatedNode(parent)
return
}
case ast.KindParameter:
if parent.Type() == nil && tag.AsJSDocTypeTag().TypeExpression != nil {
parent.AsMutable().SetType(p.reparseJSDocTypeLiteral(tag.AsJSDocTypeTag().TypeExpression.Type()))
p.finishMutatedNode(parent)
return
}
case ast.KindExpressionStatement:
if parent.AsExpressionStatement().Expression.Kind == ast.KindBinaryExpression {
bin := parent.AsExpressionStatement().Expression.AsBinaryExpression()
if kind := ast.GetAssignmentDeclarationKind(bin); kind != ast.JSDeclarationKindNone && tag.AsJSDocTypeTag().TypeExpression != nil {
bin.AsMutable().SetType(p.factory.DeepCloneReparse(tag.AsJSDocTypeTag().TypeExpression.Type()))
p.finishMutatedNode(bin.AsNode())
return
}
}
case ast.KindReturnStatement, ast.KindParenthesizedExpression:
if tag.AsJSDocTypeTag().TypeExpression != nil {
parent.AsMutable().SetExpression(p.makeNewCast(
p.factory.DeepCloneReparse(tag.AsJSDocTypeTag().TypeExpression.Type()),
p.factory.DeepCloneReparse(parent.Expression()),
true /*isAssertion*/))
p.finishMutatedNode(parent)
return
}
}
if fun, ok := getFunctionLikeHost(parent); ok {
noTypedParams := core.Every(fun.Parameters(), func(param *ast.Node) bool { return param.Type() == nil })
if fun.Type() == nil && noTypedParams && tag.AsJSDocTypeTag().TypeExpression != nil {
fun.FunctionLikeData().FullSignature = p.factory.DeepCloneReparse(tag.AsJSDocTypeTag().TypeExpression.Type())
p.finishMutatedNode(fun)
}
}
case ast.KindJSDocSatisfiesTag:
switch parent.Kind {
case ast.KindVariableStatement:
if parent.AsVariableStatement().DeclarationList != nil {
for _, declaration := range parent.AsVariableStatement().DeclarationList.AsVariableDeclarationList().Declarations.Nodes {
if declaration.Initializer() != nil && tag.AsJSDocSatisfiesTag().TypeExpression != nil {
declaration.AsMutable().SetInitializer(p.makeNewCast(
p.factory.DeepCloneReparse(tag.AsJSDocSatisfiesTag().TypeExpression.Type()),
p.factory.DeepCloneReparse(declaration.Initializer()),
false /*isAssertion*/))
p.finishMutatedNode(declaration)
break
}
}
}
case ast.KindVariableDeclaration,
ast.KindCommonJSExport,
ast.KindPropertyDeclaration, ast.KindPropertyAssignment, ast.KindShorthandPropertyAssignment:
if parent.Initializer() != nil && tag.AsJSDocSatisfiesTag().TypeExpression != nil {
parent.AsMutable().SetInitializer(p.makeNewCast(
p.factory.DeepCloneReparse(tag.AsJSDocSatisfiesTag().TypeExpression.Type()),
p.factory.DeepCloneReparse(parent.Initializer()),
false /*isAssertion*/))
p.finishMutatedNode(parent)
}
case ast.KindReturnStatement, ast.KindParenthesizedExpression,
ast.KindExportAssignment, ast.KindJSExportAssignment:
if parent.Expression() != nil && tag.AsJSDocSatisfiesTag().TypeExpression != nil {
parent.AsMutable().SetExpression(p.makeNewCast(
p.factory.DeepCloneReparse(tag.AsJSDocSatisfiesTag().TypeExpression.Type()),
p.factory.DeepCloneReparse(parent.Expression()),
false /*isAssertion*/))
p.finishMutatedNode(parent)
}
case ast.KindExpressionStatement:
if parent.AsExpressionStatement().Expression.Kind == ast.KindBinaryExpression {
bin := parent.AsExpressionStatement().Expression.AsBinaryExpression()
if kind := ast.GetAssignmentDeclarationKind(bin); kind != ast.JSDeclarationKindNone && tag.AsJSDocSatisfiesTag().TypeExpression != nil {
bin.Right = p.makeNewCast(
p.factory.DeepCloneReparse(tag.AsJSDocSatisfiesTag().TypeExpression.Type()),
p.factory.DeepCloneReparse(bin.Right),
false /*isAssertion*/)
p.finishMutatedNode(bin.AsNode())
}
}
}
case ast.KindJSDocTemplateTag:
if fun, ok := getFunctionLikeHost(parent); ok {
if fun.TypeParameters() == nil {
fun.FunctionLikeData().TypeParameters = p.gatherTypeParameters(jsDoc, nil /*tagWithTypeParameters*/)
p.finishMutatedNode(fun)
}
} else if parent.Kind == ast.KindClassDeclaration {
class := parent.AsClassDeclaration()
if class.TypeParameters == nil {
class.TypeParameters = p.gatherTypeParameters(jsDoc, nil /*tagWithTypeParameters*/)
p.finishMutatedNode(parent)
}
} else if parent.Kind == ast.KindClassExpression {
class := parent.AsClassExpression()
if class.TypeParameters == nil {
class.TypeParameters = p.gatherTypeParameters(jsDoc, nil /*tagWithTypeParameters*/)
p.finishMutatedNode(parent)
}
}
case ast.KindJSDocParameterTag:
if fun, ok := getFunctionLikeHost(parent); ok {
parameterTag := tag.AsJSDocParameterOrPropertyTag()
if param, ok := findMatchingParameter(fun, parameterTag, jsDoc); ok {
if param.Type == nil && parameterTag.TypeExpression != nil {
param.AsParameterDeclaration().Type = p.reparseJSDocTypeLiteral(parameterTag.TypeExpression.Type())
}
if param.QuestionToken == nil && param.Initializer == nil {
if question := p.makeQuestionIfOptional(parameterTag); question != nil {
param.QuestionToken = question
}
}
p.finishMutatedNode(param.AsNode())
}
}
case ast.KindJSDocThisTag:
if fun, ok := getFunctionLikeHost(parent); ok {
params := fun.Parameters()
if len(params) == 0 || params[0].Name().Kind != ast.KindThisKeyword {
thisParam := p.factory.NewParameterDeclaration(
nil, /* decorators */
nil, /* modifiers */
p.factory.NewIdentifier("this"),
nil, /* questionToken */
nil, /* type */
nil, /* initializer */
)
if tag.AsJSDocThisTag().TypeExpression != nil {
thisParam.AsParameterDeclaration().Type = p.factory.DeepCloneReparse(tag.AsJSDocThisTag().TypeExpression.Type())
}
p.finishReparsedNode(thisParam, tag.AsJSDocThisTag().TagName)
newParams := p.nodeSlicePool.NewSlice(len(params) + 1)
newParams[0] = thisParam
for i, param := range params {
newParams[i+1] = param
}
fun.FunctionLikeData().Parameters = p.newNodeList(thisParam.Loc, newParams)
p.finishMutatedNode(fun)
}
}
case ast.KindJSDocReturnTag:
if fun, ok := getFunctionLikeHost(parent); ok {
if fun.Type() == nil && tag.AsJSDocReturnTag().TypeExpression != nil {
fun.FunctionLikeData().Type = p.factory.DeepCloneReparse(tag.AsJSDocReturnTag().TypeExpression.Type())
p.finishMutatedNode(fun)
}
}
case ast.KindJSDocReadonlyTag, ast.KindJSDocPrivateTag, ast.KindJSDocPublicTag, ast.KindJSDocProtectedTag, ast.KindJSDocOverrideTag:
if parent.Kind == ast.KindExpressionStatement {
parent = parent.AsExpressionStatement().Expression
}
switch parent.Kind {
case ast.KindPropertyDeclaration, ast.KindMethodDeclaration, ast.KindGetAccessor, ast.KindSetAccessor, ast.KindBinaryExpression:
var keyword ast.Kind
switch tag.Kind {
case ast.KindJSDocReadonlyTag:
keyword = ast.KindReadonlyKeyword
case ast.KindJSDocPrivateTag:
keyword = ast.KindPrivateKeyword
case ast.KindJSDocPublicTag:
keyword = ast.KindPublicKeyword
case ast.KindJSDocProtectedTag:
keyword = ast.KindProtectedKeyword
case ast.KindJSDocOverrideTag:
keyword = ast.KindOverrideKeyword
}
modifier := p.factory.NewModifier(keyword)
modifier.Loc = tag.Loc
modifier.Flags = p.contextFlags | ast.NodeFlagsReparsed
var nodes []*ast.Node
var loc core.TextRange
if parent.Modifiers() == nil {
nodes = p.nodeSlicePool.NewSlice(1)
nodes[0] = modifier
loc = tag.Loc
} else {
nodes = append(parent.Modifiers().Nodes, modifier)
loc = parent.Modifiers().Loc
}
parent.AsMutable().SetModifiers(p.newModifierList(loc, nodes))
p.finishMutatedNode(parent)
}
case ast.KindJSDocImplementsTag:
if class := getClassLikeData(parent); class != nil {
implementsTag := tag.AsJSDocImplementsTag()
if class.HeritageClauses != nil {
if implementsClause := core.Find(class.HeritageClauses.Nodes, func(node *ast.Node) bool {
return node.AsHeritageClause().Token == ast.KindImplementsKeyword
}); implementsClause != nil {
implementsClause.AsHeritageClause().Types.Nodes = append(implementsClause.AsHeritageClause().Types.Nodes, p.factory.DeepCloneReparse(implementsTag.ClassName))
p.finishMutatedNode(implementsClause)
return
}
}
typesList := p.newNodeList(implementsTag.ClassName.Loc, p.nodeSlicePool.NewSlice1(p.factory.DeepCloneReparse(implementsTag.ClassName)))
heritageClause := p.factory.NewHeritageClause(ast.KindImplementsKeyword, typesList)
p.finishReparsedNode(heritageClause, implementsTag.ClassName)
if class.HeritageClauses == nil {
heritageClauses := p.newNodeList(implementsTag.ClassName.Loc, p.nodeSlicePool.NewSlice1(heritageClause))
class.HeritageClauses = heritageClauses
} else {
class.HeritageClauses.Nodes = append(class.HeritageClauses.Nodes, heritageClause)
}
p.finishMutatedNode(parent)
}
case ast.KindJSDocAugmentsTag:
if class := getClassLikeData(parent); class != nil && class.HeritageClauses != nil {
if extendsClause := core.Find(class.HeritageClauses.Nodes, func(node *ast.Node) bool {
return node.AsHeritageClause().Token == ast.KindExtendsKeyword
}); extendsClause != nil && len(extendsClause.AsHeritageClause().Types.Nodes) == 1 {
target := extendsClause.AsHeritageClause().Types.Nodes[0].AsExpressionWithTypeArguments()
source := tag.AsJSDocAugmentsTag().ClassName.AsExpressionWithTypeArguments()
if ast.HasSamePropertyAccessName(target.Expression, source.Expression) {
if target.TypeArguments == nil && source.TypeArguments != nil {
newArguments := p.nodeSlicePool.NewSlice(len(source.TypeArguments.Nodes))
for i, arg := range source.TypeArguments.Nodes {
newArguments[i] = p.factory.DeepCloneReparse(arg)
}
target.TypeArguments = p.newNodeList(source.TypeArguments.Loc, newArguments)
p.finishMutatedNode(target.AsNode())
}
}
}
}
}
}
func (p *Parser) makeQuestionIfOptional(parameter *ast.JSDocParameterTag) *ast.Node {
var questionToken *ast.Node
if parameter.IsBracketed || parameter.TypeExpression != nil && parameter.TypeExpression.Type().Kind == ast.KindJSDocOptionalType {
questionToken = p.factory.NewToken(ast.KindQuestionToken)
questionToken.Loc = parameter.Loc
questionToken.Flags = p.contextFlags | ast.NodeFlagsReparsed
}
return questionToken
}
func findMatchingParameter(fun *ast.Node, parameterTag *ast.JSDocParameterTag, jsDoc *ast.Node) (*ast.ParameterDeclaration, bool) {
tagIndex := -1
paramCount := -1
for _, tag := range jsDoc.AsJSDoc().Tags.Nodes {
if tag.Kind == ast.KindJSDocParameterTag {
paramCount++
if tag.AsJSDocParameterOrPropertyTag() == parameterTag {
tagIndex = paramCount
break
}
}
}
for parameterIndex, parameter := range fun.Parameters() {
if parameter.Name().Kind == ast.KindIdentifier {
if parameterTag.Name().Kind == ast.KindIdentifier && parameter.Name().Text() == parameterTag.Name().Text() {
return parameter.AsParameterDeclaration(), true
}
} else if parameterIndex == tagIndex {
return parameter.AsParameterDeclaration(), true
}
}
return nil, false
}
func getFunctionLikeHost(host *ast.Node) (*ast.Node, bool) {
fun := host
if host.Kind == ast.KindVariableStatement && host.AsVariableStatement().DeclarationList != nil {
for _, declaration := range host.AsVariableStatement().DeclarationList.AsVariableDeclarationList().Declarations.Nodes {
if ast.IsFunctionLike(declaration.Initializer()) {
fun = declaration.Initializer()
break
}
}
} else if host.Kind == ast.KindPropertyAssignment {
fun = host.AsPropertyAssignment().Initializer
} else if host.Kind == ast.KindPropertyDeclaration {
fun = host.AsPropertyDeclaration().Initializer
} else if host.Kind == ast.KindExportAssignment {
fun = host.AsExportAssignment().Expression
} else if host.Kind == ast.KindReturnStatement {
fun = host.AsReturnStatement().Expression
}
if ast.IsFunctionLike(fun) {
return fun, true
}
return nil, false
}
func (p *Parser) makeNewCast(t *ast.TypeNode, e *ast.Node, isAssertion bool) *ast.Node {
var assert *ast.Node
if isAssertion {
assert = p.factory.NewAsExpression(e, t)
} else {
assert = p.factory.NewSatisfiesExpression(e, t)
}
p.finishReparsedNode(assert, e)
return assert
}
func getClassLikeData(parent *ast.Node) *ast.ClassLikeBase {
var class *ast.ClassLikeBase
switch parent.Kind {
case ast.KindClassDeclaration:
class = parent.AsClassDeclaration().ClassLikeData()
case ast.KindClassExpression:
class = parent.AsClassExpression().ClassLikeData()
}
return class
}

View File

@ -0,0 +1,14 @@
package parser
// ParseFlags
type ParseFlags uint32
const (
ParseFlagsNone ParseFlags = 0
ParseFlagsYield ParseFlags = 1 << 0
ParseFlagsAwait ParseFlags = 1 << 1
ParseFlagsType ParseFlags = 1 << 2
ParseFlagsIgnoreMissingOpenBrace ParseFlags = 1 << 4
ParseFlagsJSDoc ParseFlags = 1 << 5
)

View File

@ -0,0 +1,54 @@
package parser
import (
"slices"
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/ast"
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/core"
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/scanner"
)
func getLanguageVariant(scriptKind core.ScriptKind) core.LanguageVariant {
switch scriptKind {
case core.ScriptKindTSX, core.ScriptKindJSX, core.ScriptKindJS, core.ScriptKindJSON:
// .tsx and .jsx files are treated as jsx language variant.
return core.LanguageVariantJSX
}
return core.LanguageVariantStandard
}
func tokenIsIdentifierOrKeyword(token ast.Kind) bool {
return token >= ast.KindIdentifier
}
func tokenIsIdentifierOrKeywordOrGreaterThan(token ast.Kind) bool {
return token == ast.KindGreaterThanToken || tokenIsIdentifierOrKeyword(token)
}
func GetJSDocCommentRanges(f *ast.NodeFactory, commentRanges []ast.CommentRange, node *ast.Node, text string) []ast.CommentRange {
switch node.Kind {
case ast.KindParameter, ast.KindTypeParameter, ast.KindFunctionExpression, ast.KindArrowFunction, ast.KindParenthesizedExpression, ast.KindVariableDeclaration, ast.KindExportSpecifier:
for commentRange := range scanner.GetTrailingCommentRanges(f, text, node.Pos()) {
commentRanges = append(commentRanges, commentRange)
}
for commentRange := range scanner.GetLeadingCommentRanges(f, text, node.Pos()) {
commentRanges = append(commentRanges, commentRange)
}
default:
for commentRange := range scanner.GetLeadingCommentRanges(f, text, node.Pos()) {
commentRanges = append(commentRanges, commentRange)
}
}
// Keep if the comment starts with '/**' but not if it is '/**/'
return slices.DeleteFunc(commentRanges, func(comment ast.CommentRange) bool {
return comment.End() > node.End() || text[comment.Pos()+1] != '*' || text[comment.Pos()+2] != '*' || text[comment.Pos()+3] == '/'
})
}
func isKeywordOrPunctuation(token ast.Kind) bool {
return ast.IsKeywordKind(token) || ast.IsPunctuationKind(token)
}
func isJSDocLikeText(text string) bool {
return len(text) >= 4 && text[1] == '*' && text[2] == '*' && text[3] != '/'
}

View File

@ -0,0 +1,63 @@
package repo
import (
"os"
"path/filepath"
"runtime"
"sync"
)
var (
RootPath string
TypeScriptSubmodulePath string
TestDataPath string
)
func init() {
_, filename, _, ok := runtime.Caller(0)
if !ok {
panic("could not get current filename")
}
filename = filepath.FromSlash(filename) // runtime.Caller always returns forward slashes; https://go.dev/issues/3335, https://go.dev/cl/603275
RootPath = findGoMod(filepath.Dir(filename))
TypeScriptSubmodulePath = filepath.Join(RootPath, "_submodules", "TypeScript")
TestDataPath = filepath.Join(RootPath, "testdata")
}
func findGoMod(dir string) string {
root := filepath.VolumeName(dir)
for dir != root {
if _, err := os.Stat(filepath.Join(dir, "go.mod")); err == nil {
return dir
}
dir = filepath.Dir(dir)
}
panic("could not find go.mod")
}
var typeScriptSubmoduleExists = sync.OnceValue(func() bool {
p := filepath.Join(TypeScriptSubmodulePath, "package.json")
if _, err := os.Stat(p); err != nil {
if os.IsNotExist(err) {
return false
}
panic(err)
}
return true
})
func TypeScriptSubmoduleExists() bool {
return typeScriptSubmoduleExists()
}
type skippable interface {
Helper()
Skipf(format string, args ...any)
}
func SkipIfNoTypeScriptSubmodule(t skippable) {
t.Helper()
if !typeScriptSubmoduleExists() {
t.Skipf("TypeScript submodule does not exist")
}
}

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,67 @@
package scanner
import (
"strings"
"unicode/utf8"
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/ast"
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/core"
)
func tokenIsIdentifierOrKeyword(token ast.Kind) bool {
return token >= ast.KindIdentifier
}
func IdentifierToKeywordKind(node *ast.Identifier) ast.Kind {
return textToKeyword[node.Text]
}
func GetSourceTextOfNodeFromSourceFile(sourceFile *ast.SourceFile, node *ast.Node, includeTrivia bool) string {
return GetTextOfNodeFromSourceText(sourceFile.Text(), node, includeTrivia)
}
func GetTextOfNodeFromSourceText(sourceText string, node *ast.Node, includeTrivia bool) string {
if ast.NodeIsMissing(node) {
return ""
}
pos := node.Pos()
if !includeTrivia {
pos = SkipTrivia(sourceText, pos)
}
text := sourceText[pos:node.End()]
// if (isJSDocTypeExpressionOrChild(node)) {
// // strip space + asterisk at line start
// text = text.split(/\r\n|\n|\r/).map(line => line.replace(/^\s*\*/, "").trimStart()).join("\n");
// }
return text
}
func GetTextOfNode(node *ast.Node) string {
return GetSourceTextOfNodeFromSourceFile(ast.GetSourceFileOfNode(node), node, false /*includeTrivia*/)
}
func DeclarationNameToString(name *ast.Node) string {
if name == nil || name.Pos() == name.End() {
return "(Missing)"
}
return GetTextOfNode(name)
}
func IsIdentifierText(name string, languageVariant core.LanguageVariant) bool {
ch, size := utf8.DecodeRuneInString(name)
if !IsIdentifierStart(ch) {
return false
}
for i := size; i < len(name); {
ch, size = utf8.DecodeRuneInString(name[i:])
if !IsIdentifierPartEx(ch, languageVariant) {
return false
}
i += size
}
return true
}
func IsIntrinsicJsxName(name string) bool {
return len(name) != 0 && (name[0] >= 'a' && name[0] <= 'z' || strings.ContainsRune(name, '-'))
}

View File

@ -0,0 +1,120 @@
package stringutil
import (
"strings"
"unicode"
"unicode/utf8"
)
func EquateStringCaseInsensitive(a, b string) bool {
// !!!
// return a == b || strings.ToUpper(a) == strings.ToUpper(b)
return strings.EqualFold(a, b)
}
func EquateStringCaseSensitive(a, b string) bool {
return a == b
}
func GetStringEqualityComparer(ignoreCase bool) func(a, b string) bool {
if ignoreCase {
return EquateStringCaseInsensitive
}
return EquateStringCaseSensitive
}
type Comparison = int
const (
ComparisonLessThan Comparison = -1
ComparisonEqual Comparison = 0
ComparisonGreaterThan Comparison = 1
)
func CompareStringsCaseInsensitive(a string, b string) Comparison {
if a == b {
return ComparisonEqual
}
for {
ca, sa := utf8.DecodeRuneInString(a)
cb, sb := utf8.DecodeRuneInString(b)
if sa == 0 {
if sb == 0 {
return ComparisonEqual
}
return ComparisonLessThan
}
if sb == 0 {
return ComparisonGreaterThan
}
lca := unicode.ToLower(ca)
lcb := unicode.ToLower(cb)
if lca != lcb {
if lca < lcb {
return ComparisonLessThan
}
return ComparisonGreaterThan
}
a = a[sa:]
b = b[sb:]
}
}
func CompareStringsCaseSensitive(a string, b string) Comparison {
return strings.Compare(a, b)
}
func GetStringComparer(ignoreCase bool) func(a, b string) Comparison {
if ignoreCase {
return CompareStringsCaseInsensitive
}
return CompareStringsCaseSensitive
}
func HasPrefix(s string, prefix string, caseSensitive bool) bool {
if caseSensitive {
return strings.HasPrefix(s, prefix)
}
if len(prefix) > len(s) {
return false
}
return strings.EqualFold(s[0:len(prefix)], prefix)
}
func HasSuffix(s string, suffix string, caseSensitive bool) bool {
if caseSensitive {
return strings.HasSuffix(s, suffix)
}
if len(suffix) > len(s) {
return false
}
return strings.EqualFold(s[len(s)-len(suffix):], suffix)
}
func CompareStringsCaseInsensitiveThenSensitive(a, b string) Comparison {
cmp := CompareStringsCaseInsensitive(a, b)
if cmp != ComparisonEqual {
return cmp
}
return CompareStringsCaseSensitive(a, b)
}
// CompareStringsCaseInsensitiveEslintCompatible performs a case-insensitive comparison
// using toLowerCase() instead of toUpperCase() for ESLint compatibility.
//
// `CompareStringsCaseInsensitive` transforms letters to uppercase for unicode reasons,
// while eslint's `sort-imports` rule transforms letters to lowercase. Which one you choose
// affects the relative order of letters and ASCII characters 91-96, of which `_` is a
// valid character in an identifier. So if we used `CompareStringsCaseInsensitive` for
// import sorting, TypeScript and eslint would disagree about the correct case-insensitive
// sort order for `__String` and `Foo`. Since eslint's whole job is to create consistency
// by enforcing nitpicky details like this, it makes way more sense for us to just adopt
// their convention so users can have auto-imports without making eslint angry.
func CompareStringsCaseInsensitiveEslintCompatible(a, b string) Comparison {
if a == b {
return ComparisonEqual
}
a = strings.ToLower(a)
b = strings.ToLower(b)
return strings.Compare(a, b)
}

View File

@ -0,0 +1,19 @@
package stringutil
import (
"fmt"
"regexp"
"strconv"
)
var placeholderRegexp = regexp.MustCompile(`{(\d+)}`)
func Format(text string, args []any) string {
return placeholderRegexp.ReplaceAllStringFunc(text, func(match string) string {
index, err := strconv.ParseInt(match[1:len(match)-1], 10, 0)
if err != nil || int(index) >= len(args) {
panic("Invalid formatting placeholder")
}
return fmt.Sprintf("%v", args[int(index)])
})
}

View File

@ -0,0 +1,240 @@
// Package stringutil Exports common rune utilities for parsing and emitting javascript
package stringutil
import (
"net/url"
"regexp"
"strings"
"unicode"
"unicode/utf8"
)
func IsWhiteSpaceLike(ch rune) bool {
return IsWhiteSpaceSingleLine(ch) || IsLineBreak(ch)
}
func IsWhiteSpaceSingleLine(ch rune) bool {
// Note: nextLine is in the Zs space, and should be considered to be a whitespace.
// It is explicitly not a line-break as it isn't in the exact set specified by EcmaScript.
switch ch {
case
' ', // space
'\t', // tab
'\v', // verticalTab
'\f', // formFeed
0x0085, // nextLine
0x00A0, // nonBreakingSpace
0x1680, // ogham
0x2000, // enQuad
0x2001, // emQuad
0x2002, // enSpace
0x2003, // emSpace
0x2004, // threePerEmSpace
0x2005, // fourPerEmSpace
0x2006, // sixPerEmSpace
0x2007, // figureSpace
0x2008, // punctuationEmSpace
0x2009, // thinSpace
0x200A, // hairSpace
0x200B, // zeroWidthSpace
0x202F, // narrowNoBreakSpace
0x205F, // mathematicalSpace
0x3000, // ideographicSpace
0xFEFF: // byteOrderMark
return true
}
return false
}
func IsLineBreak(ch rune) bool {
// ES5 7.3:
// The ECMAScript line terminator characters are listed in Table 3.
// Table 3: Line Terminator Characters
// Code Unit Value Name Formal Name
// \u000A Line Feed <LF>
// \u000D Carriage Return <CR>
// \u2028 Line separator <LS>
// \u2029 Paragraph separator <PS>
// Only the characters in Table 3 are treated as line terminators. Other new line or line
// breaking characters are treated as white space but not as line terminators.
switch ch {
case
'\n', // lineFeed
'\r', // carriageReturn
0x2028, // lineSeparator
0x2029: // paragraphSeparator
return true
}
return false
}
func IsDigit(ch rune) bool {
return ch >= '0' && ch <= '9'
}
func IsOctalDigit(ch rune) bool {
return ch >= '0' && ch <= '7'
}
func IsHexDigit(ch rune) bool {
return ch >= '0' && ch <= '9' || ch >= 'A' && ch <= 'F' || ch >= 'a' && ch <= 'f'
}
func IsASCIILetter(ch rune) bool {
return ch >= 'A' && ch <= 'Z' || ch >= 'a' && ch <= 'z'
}
func SplitLines(text string) []string {
lines := make([]string, 0, strings.Count(text, "\n")+1) // preallocate
start := 0
pos := 0
for pos < len(text) {
switch text[pos] {
case '\r':
if pos+1 < len(text) && text[pos+1] == '\n' {
lines = append(lines, text[start:pos])
pos += 2
start = pos
continue
}
fallthrough
case '\n':
lines = append(lines, text[start:pos])
pos++
start = pos
continue
}
pos++
}
if start < len(text) {
lines = append(lines, text[start:])
}
return lines
}
func GuessIndentation(lines []string) int {
const MAX_SMI_X86 int = 0x3fff_ffff
indentation := MAX_SMI_X86
for _, line := range lines {
if len(line) == 0 {
continue
}
i := 0
for i < len(line) && i < indentation {
ch, size := utf8.DecodeRuneInString(line[i:])
if !IsWhiteSpaceLike(ch) {
break
}
i += size
}
if i < indentation {
indentation = i
}
if indentation == 0 {
return 0
}
}
if indentation == MAX_SMI_X86 {
return 0
}
return indentation
}
// https://tc39.es/ecma262/multipage/global-object.html#sec-encodeuri-uri
func EncodeURI(s string) string {
var builder strings.Builder
start := 0
pos := indexAny(s, ";/?:@&=+$,#", 0)
for pos >= 0 {
builder.WriteString(url.QueryEscape(s[start:pos]))
builder.WriteString(s[pos : pos+1])
start = pos + 1
pos = indexAny(s, ";/?:@&=+$,#", start)
}
if start < len(s) {
builder.WriteString(url.QueryEscape(s[start:]))
}
return builder.String()
}
func indexAny(s, chars string, start int) int {
if start < 0 || start >= len(s) {
return -1
}
index := strings.IndexAny(s[start:], chars)
if index < 0 {
return -1
}
return start + index
}
func getByteOrderMarkLength(text string) int {
if len(text) >= 1 {
ch0 := text[0]
if ch0 == 0xfe {
if len(text) >= 2 && text[1] == 0xff {
return 2 // utf16be
}
return 0
}
if ch0 == 0xff {
if len(text) >= 2 && text[1] == 0xfe {
return 2 // utf16le
}
return 0
}
if ch0 == 0xef {
if len(text) >= 3 && text[1] == 0xbb && text[2] == 0xbf {
return 3 // utf8
}
return 0
}
}
return 0
}
func RemoveByteOrderMark(text string) string {
length := getByteOrderMarkLength(text)
if length > 0 {
return text[length:]
}
return text
}
func AddUTF8ByteOrderMark(text string) string {
if getByteOrderMarkLength(text) == 0 {
return "\xEF\xBB\xBF" + text
}
return text
}
func StripQuotes(name string) string {
firstChar, _ := utf8.DecodeRuneInString(name)
lastChar, _ := utf8.DecodeLastRuneInString(name)
if firstChar == lastChar && (firstChar == '\'' || firstChar == '"' || firstChar == '`') {
return name[1 : len(name)-1]
}
return name
}
var matchSlashSomething = regexp.MustCompile(`\.`)
func matchSlashReplacer(in string) string {
return in[1:]
}
func UnquoteString(str string) string {
// strconv.Unquote is insufficient as that only handles a single character inside single quotes, as those are character literals in go
inner := StripQuotes(str)
// In strada we do str.replace(/\\./g, s => s.substring(1)) - which is to say, replace all backslash-something with just something
// That's replicated here faithfully, but it seems wrong! This should probably be an actual unquote operation?
return matchSlashSomething.ReplaceAllStringFunc(inner, matchSlashReplacer)
}
func LowerFirstChar(str string) string {
char, size := utf8.DecodeRuneInString(str)
if size > 0 {
return string(unicode.ToLower(char)) + str[size:]
}
return str
}

Some files were not shown because too many files have changed in this diff Show More