squash
This commit is contained in:
parent
3d66a03e2e
commit
269682e4ab
5
.gitignore
vendored
5
.gitignore
vendored
@ -1 +1,6 @@
|
||||
/.idea
|
||||
/go.work
|
||||
/go.work.sum
|
||||
node_modules
|
||||
*.js
|
||||
/TODO.md
|
||||
|
||||
201
LICENSE
Normal file
201
LICENSE
Normal 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
66
README.md
Normal 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
7
example/Makefile
Normal 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
3
example/golang/go.mod
Normal file
@ -0,0 +1,3 @@
|
||||
module efprojects.com/kitten-ipc/example/simple
|
||||
|
||||
go 1.25.1
|
||||
54
example/golang/main.go
Normal file
54
example/golang/main.go
Normal 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
25
example/golang/remote.go
Normal 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
7
example/ts/dist/goapi.gen.d.ts
vendored
Normal 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
1
example/ts/dist/goapi.gen.d.ts.map
vendored
Normal 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
1
example/ts/dist/goapi.gen.js.map
vendored
Normal 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
2
example/ts/dist/index.d.ts
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
export {};
|
||||
//# sourceMappingURL=index.d.ts.map
|
||||
1
example/ts/dist/index.d.ts.map
vendored
Normal file
1
example/ts/dist/index.d.ts.map
vendored
Normal 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
1
example/ts/dist/index.js.map
vendored
Normal 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
50
example/ts/package-lock.json
generated
Normal 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
12
example/ts/package.json
Normal 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
30
example/ts/src/index.ts
Normal 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
15
example/ts/src/remote.ts
Normal 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
28
example/ts/tsconfig.json
Normal 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
56
kitcom/Makefile
Normal 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
35
kitcom/api/api.go
Normal 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
9
kitcom/go.mod
Normal 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
6
kitcom/go.sum
Normal 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
34
kitcom/golang/go_gen.tmpl
Normal 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
92
kitcom/golang/gogen.go
Normal 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
163
kitcom/golang/goparser.go
Normal 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
|
||||
}
|
||||
11138
kitcom/internal/tsgo/ast/ast.go
Normal file
11138
kitcom/internal/tsgo/ast/ast.go
Normal file
File diff suppressed because it is too large
Load Diff
35
kitcom/internal/tsgo/ast/checkflags.go
Normal file
35
kitcom/internal/tsgo/ast/checkflags.go
Normal 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
|
||||
)
|
||||
86
kitcom/internal/tsgo/ast/deepclone.go
Normal file
86
kitcom/internal/tsgo/ast/deepclone.go
Normal 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)
|
||||
}
|
||||
272
kitcom/internal/tsgo/ast/diagnostic.go
Normal file
272
kitcom/internal/tsgo/ast/diagnostic.go
Normal 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())
|
||||
}
|
||||
75
kitcom/internal/tsgo/ast/flow.go
Normal file
75
kitcom/internal/tsgo/ast/flow.go
Normal 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{})
|
||||
}
|
||||
6
kitcom/internal/tsgo/ast/ids.go
Normal file
6
kitcom/internal/tsgo/ast/ids.go
Normal file
@ -0,0 +1,6 @@
|
||||
package ast
|
||||
|
||||
type (
|
||||
NodeId uint64
|
||||
SymbolId uint64
|
||||
)
|
||||
430
kitcom/internal/tsgo/ast/kind.go
Normal file
430
kitcom/internal/tsgo/ast/kind.go
Normal 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
|
||||
)
|
||||
378
kitcom/internal/tsgo/ast/kind_stringer_generated.go
Normal file
378
kitcom/internal/tsgo/ast/kind_stringer_generated.go
Normal file
File diff suppressed because one or more lines are too long
53
kitcom/internal/tsgo/ast/modifierflags.go
Normal file
53
kitcom/internal/tsgo/ast/modifierflags.go
Normal 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
|
||||
)
|
||||
65
kitcom/internal/tsgo/ast/nodeflags.go
Normal file
65
kitcom/internal/tsgo/ast/nodeflags.go
Normal 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
|
||||
)
|
||||
168
kitcom/internal/tsgo/ast/parseoptions.go
Normal file
168
kitcom/internal/tsgo/ast/parseoptions.go
Normal 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
|
||||
}
|
||||
714
kitcom/internal/tsgo/ast/precedence.go
Normal file
714
kitcom/internal/tsgo/ast/precedence.go
Normal 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))
|
||||
}
|
||||
}
|
||||
131
kitcom/internal/tsgo/ast/subtreefacts.go
Normal file
131
kitcom/internal/tsgo/ast/subtreefacts.go
Normal 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)
|
||||
}
|
||||
59
kitcom/internal/tsgo/ast/symbol.go
Normal file
59
kitcom/internal/tsgo/ast/symbol.go
Normal 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
|
||||
}
|
||||
86
kitcom/internal/tsgo/ast/symbolflags.go
Normal file
86
kitcom/internal/tsgo/ast/symbolflags.go
Normal 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
|
||||
)
|
||||
31
kitcom/internal/tsgo/ast/tokenflags.go
Normal file
31
kitcom/internal/tsgo/ast/tokenflags.go
Normal 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
|
||||
)
|
||||
3892
kitcom/internal/tsgo/ast/utilities.go
Normal file
3892
kitcom/internal/tsgo/ast/utilities.go
Normal file
File diff suppressed because it is too large
Load Diff
278
kitcom/internal/tsgo/ast/visitor.go
Normal file
278
kitcom/internal/tsgo/ast/visitor.go
Normal 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
|
||||
}
|
||||
69
kitcom/internal/tsgo/collections/multimap.go
Normal file
69
kitcom/internal/tsgo/collections/multimap.go
Normal 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)
|
||||
}
|
||||
317
kitcom/internal/tsgo/collections/ordered_map.go
Normal file
317
kitcom/internal/tsgo/collections/ordered_map.go
Normal 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
54
kitcom/internal/tsgo/collections/ordered_set.go
Normal file
54
kitcom/internal/tsgo/collections/ordered_set.go
Normal 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(),
|
||||
}
|
||||
}
|
||||
77
kitcom/internal/tsgo/collections/set.go
Normal file
77
kitcom/internal/tsgo/collections/set.go
Normal 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
|
||||
}
|
||||
84
kitcom/internal/tsgo/collections/syncmap.go
Normal file
84
kitcom/internal/tsgo/collections/syncmap.go
Normal 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
|
||||
}
|
||||
67
kitcom/internal/tsgo/collections/syncset.go
Normal file
67
kitcom/internal/tsgo/collections/syncset.go
Normal 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
|
||||
})
|
||||
}
|
||||
}
|
||||
206
kitcom/internal/tsgo/core/bfs.go
Normal file
206
kitcom/internal/tsgo/core/bfs.go
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
||||
26
kitcom/internal/tsgo/core/binarysearch.go
Normal file
26
kitcom/internal/tsgo/core/binarysearch.go
Normal 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
|
||||
}
|
||||
15
kitcom/internal/tsgo/core/buildoptions.go
Normal file
15
kitcom/internal/tsgo/core/buildoptions.go
Normal 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"`
|
||||
}
|
||||
545
kitcom/internal/tsgo/core/compileroptions.go
Normal file
545
kitcom/internal/tsgo/core/compileroptions.go
Normal 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
|
||||
)
|
||||
36
kitcom/internal/tsgo/core/context.go
Normal file
36
kitcom/internal/tsgo/core/context.go
Normal 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
|
||||
}
|
||||
689
kitcom/internal/tsgo/core/core.go
Normal file
689
kitcom/internal/tsgo/core/core.go
Normal 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
|
||||
}
|
||||
11
kitcom/internal/tsgo/core/languagevariant.go
Normal file
11
kitcom/internal/tsgo/core/languagevariant.go
Normal 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
|
||||
)
|
||||
@ -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]]
|
||||
}
|
||||
30
kitcom/internal/tsgo/core/linkstore.go
Normal file
30
kitcom/internal/tsgo/core/linkstore.go
Normal 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]
|
||||
}
|
||||
52
kitcom/internal/tsgo/core/modulekind_stringer_generated.go
Normal file
52
kitcom/internal/tsgo/core/modulekind_stringer_generated.go
Normal 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) + ")"
|
||||
}
|
||||
}
|
||||
88
kitcom/internal/tsgo/core/nodemodules.go
Normal file
88
kitcom/internal/tsgo/core/nodemodules.go
Normal 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
|
||||
}
|
||||
10
kitcom/internal/tsgo/core/parsedoptions.go
Normal file
10
kitcom/internal/tsgo/core/parsedoptions.go
Normal 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"`
|
||||
}
|
||||
52
kitcom/internal/tsgo/core/pattern.go
Normal file
52
kitcom/internal/tsgo/core/pattern.go
Normal 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
|
||||
}
|
||||
66
kitcom/internal/tsgo/core/pool.go
Normal file
66
kitcom/internal/tsgo/core/pool.go
Normal 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
|
||||
}
|
||||
20
kitcom/internal/tsgo/core/projectreference.go
Normal file
20
kitcom/internal/tsgo/core/projectreference.go
Normal 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")
|
||||
}
|
||||
21
kitcom/internal/tsgo/core/scriptkind.go
Normal file
21
kitcom/internal/tsgo/core/scriptkind.go
Normal 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
|
||||
)
|
||||
30
kitcom/internal/tsgo/core/scriptkind_stringer_generated.go
Normal file
30
kitcom/internal/tsgo/core/scriptkind_stringer_generated.go
Normal 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]]
|
||||
}
|
||||
49
kitcom/internal/tsgo/core/scripttarget_stringer_generated.go
Normal file
49
kitcom/internal/tsgo/core/scripttarget_stringer_generated.go
Normal 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) + ")"
|
||||
}
|
||||
}
|
||||
33
kitcom/internal/tsgo/core/stack.go
Normal file
33
kitcom/internal/tsgo/core/stack.go
Normal 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)
|
||||
}
|
||||
66
kitcom/internal/tsgo/core/text.go
Normal file
66
kitcom/internal/tsgo/core/text.go
Normal 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
|
||||
}
|
||||
30
kitcom/internal/tsgo/core/textchange.go
Normal file
30
kitcom/internal/tsgo/core/textchange.go
Normal 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()
|
||||
}
|
||||
71
kitcom/internal/tsgo/core/tristate.go
Normal file
71
kitcom/internal/tsgo/core/tristate.go
Normal 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
|
||||
}
|
||||
25
kitcom/internal/tsgo/core/tristate_stringer_generated.go
Normal file
25
kitcom/internal/tsgo/core/tristate_stringer_generated.go
Normal 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]]
|
||||
}
|
||||
24
kitcom/internal/tsgo/core/typeacquisition.go
Normal file
24
kitcom/internal/tsgo/core/typeacquisition.go
Normal 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)
|
||||
}
|
||||
33
kitcom/internal/tsgo/core/version.go
Normal file
33
kitcom/internal/tsgo/core/version.go
Normal 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
|
||||
}
|
||||
53
kitcom/internal/tsgo/core/watchoptions.go
Normal file
53
kitcom/internal/tsgo/core/watchoptions.go
Normal 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
|
||||
}
|
||||
125
kitcom/internal/tsgo/core/workgroup.go
Normal file
125
kitcom/internal/tsgo/core/workgroup.go
Normal 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 ¶llelWorkGroup{}
|
||||
}
|
||||
|
||||
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()
|
||||
}
|
||||
198
kitcom/internal/tsgo/debug/debug.go
Normal file
198
kitcom/internal/tsgo/debug/debug.go
Normal 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...)
|
||||
}
|
||||
26
kitcom/internal/tsgo/debug/release.go
Normal file
26
kitcom/internal/tsgo/debug/release.go
Normal 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) {}
|
||||
43
kitcom/internal/tsgo/debug/shared.go
Normal file
43
kitcom/internal/tsgo/debug/shared.go
Normal 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))
|
||||
}
|
||||
63
kitcom/internal/tsgo/diagnostics/diagnostics.go
Normal file
63
kitcom/internal/tsgo/diagnostics/diagnostics.go
Normal 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
|
||||
}
|
||||
4255
kitcom/internal/tsgo/diagnostics/diagnostics_generated.go
Normal file
4255
kitcom/internal/tsgo/diagnostics/diagnostics_generated.go
Normal file
File diff suppressed because it is too large
Load Diff
@ -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
|
||||
}
|
||||
}
|
||||
183
kitcom/internal/tsgo/diagnostics/generate.go
Normal file
183
kitcom/internal/tsgo/diagnostics/generate.go
Normal 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
|
||||
}
|
||||
26
kitcom/internal/tsgo/diagnostics/stringer_generated.go
Normal file
26
kitcom/internal/tsgo/diagnostics/stringer_generated.go
Normal 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]]
|
||||
}
|
||||
159
kitcom/internal/tsgo/jsnum/jsnum.go
Normal file
159
kitcom/internal/tsgo/jsnum/jsnum.go
Normal 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)))
|
||||
}
|
||||
66
kitcom/internal/tsgo/jsnum/pseudobigint.go
Normal file
66
kitcom/internal/tsgo/jsnum/pseudobigint.go
Normal 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() // !!!
|
||||
}
|
||||
341
kitcom/internal/tsgo/jsnum/string.go
Normal file
341
kitcom/internal/tsgo/jsnum/string.go
Normal 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
|
||||
}
|
||||
24
kitcom/internal/tsgo/jsonutil/jsonutil.go
Normal file
24
kitcom/internal/tsgo/jsonutil/jsonutil.go
Normal 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))
|
||||
}
|
||||
1205
kitcom/internal/tsgo/parser/jsdoc.go
Normal file
1205
kitcom/internal/tsgo/parser/jsdoc.go
Normal file
File diff suppressed because it is too large
Load Diff
6587
kitcom/internal/tsgo/parser/parser.go
Normal file
6587
kitcom/internal/tsgo/parser/parser.go
Normal file
File diff suppressed because it is too large
Load Diff
74
kitcom/internal/tsgo/parser/references.go
Normal file
74
kitcom/internal/tsgo/parser/references.go
Normal 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*/)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
619
kitcom/internal/tsgo/parser/reparser.go
Normal file
619
kitcom/internal/tsgo/parser/reparser.go
Normal 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
|
||||
}
|
||||
14
kitcom/internal/tsgo/parser/types.go
Normal file
14
kitcom/internal/tsgo/parser/types.go
Normal 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
|
||||
)
|
||||
54
kitcom/internal/tsgo/parser/utilities.go
Normal file
54
kitcom/internal/tsgo/parser/utilities.go
Normal 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] != '/'
|
||||
}
|
||||
63
kitcom/internal/tsgo/repo/paths.go
Normal file
63
kitcom/internal/tsgo/repo/paths.go
Normal 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")
|
||||
}
|
||||
}
|
||||
2619
kitcom/internal/tsgo/scanner/scanner.go
Normal file
2619
kitcom/internal/tsgo/scanner/scanner.go
Normal file
File diff suppressed because one or more lines are too long
67
kitcom/internal/tsgo/scanner/utilities.go
Normal file
67
kitcom/internal/tsgo/scanner/utilities.go
Normal 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, '-'))
|
||||
}
|
||||
120
kitcom/internal/tsgo/stringutil/compare.go
Normal file
120
kitcom/internal/tsgo/stringutil/compare.go
Normal 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)
|
||||
}
|
||||
19
kitcom/internal/tsgo/stringutil/format.go
Normal file
19
kitcom/internal/tsgo/stringutil/format.go
Normal 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)])
|
||||
})
|
||||
}
|
||||
240
kitcom/internal/tsgo/stringutil/util.go
Normal file
240
kitcom/internal/tsgo/stringutil/util.go
Normal 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
Loading…
x
Reference in New Issue
Block a user