squash
This commit is contained in:
parent
3d66a03e2e
commit
269682e4ab
5
.gitignore
vendored
5
.gitignore
vendored
@ -1 +1,6 @@
|
|||||||
/.idea
|
/.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