Skip to content

Commit

Permalink
tools: TypeScript implementation, tests
Browse files Browse the repository at this point in the history
  • Loading branch information
matevz committed Apr 4, 2022
1 parent 9eb05c0 commit 470c885
Show file tree
Hide file tree
Showing 7 changed files with 1,154 additions and 76 deletions.
214 changes: 140 additions & 74 deletions docs/runtime/transactions.md

Large diffs are not rendered by default.

4 changes: 4 additions & 0 deletions tools/extract-runtime-txs/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,7 @@ func doExtractRuntimeTxs(cmd *cobra.Command, args []string) {
}

parsers.PopulateGoRefs(transactions, viper.GetString(CfgCodebasePath)+"/client-sdk/go")
parsers.PopulateTypeScriptRefs(transactions, viper.GetString(CfgCodebasePath)+"/client-sdk/ts-web")

if viper.GetBool(CfgMarkdown) {
printMarkdown(transactions)
Expand All @@ -181,6 +182,9 @@ func doExtractRuntimeTxs(cmd *cobra.Command, args []string) {
for _, w := range parsers.GolangWarnings {
fmt.Fprintln(os.Stderr, w)
}
for _, w := range parsers.TypeScriptWarnings {
fmt.Fprintln(os.Stderr, w)
}
}

// extractValue returns string value of the identifier or literal.
Expand Down
164 changes: 164 additions & 0 deletions tools/extract-runtime-txs/parsers/typescript.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
package parsers

import (
"fmt"
"log"
"os"
"path/filepath"
"regexp"
"strings"

"github.com/oasisprotocol/oasis-sdk/tools/extract-runtime-txs/types"
)

var TypeScriptWarnings = []error{}

type TypeScriptParser struct {
filename string
txs map[string]string
txParams map[string]string
txResults map[string]string
}

func NewTypeScriptParser(filename string) TypeScriptParser {
return TypeScriptParser{
filename: filename,
txs: map[string]string{},
txParams: map[string]string{},
txResults: map[string]string{},
}
}

func PopulateTypeScriptRefs(transactions map[string]types.Tx, searchDir string) error {
err := filepath.Walk(searchDir, func(path string, f os.FileInfo, err error) error {
if err != nil {
log.Fatal(err)
}
if f.IsDir() {
return nil
}
// Ts source files only, ignore types.ts, because we parse it indirectly.
if !strings.HasSuffix(f.Name(), ".ts") || f.Name() == "types.ts" {
return nil
}
tsParser := NewTypeScriptParser(path)
e := tsParser.populateTransactionRefs(transactions)
if e != nil {
return e
}

return nil
})
if err != nil {
return err
}

return nil
}

func (p *TypeScriptParser) populateTransactionRefs(txs map[string]types.Tx) error {
text, err := readFile(p.filename)
if err != nil {
return err
}

// export const METHOD_SOME_METHOD = 'some_module.SomeMethod';
regMethodMatch, _ := regexp.Compile("export const METHOD_.+ = '([a-zA-Z_\\.]+)'")
// callSomeMethod() { or querySomeMethod() {
regCallQueryMatch, _ := regexp.Compile(" (call|query)(.+)\\(\\) \\{")
// callSomeMethod() { or querySomeMethod() {
regTxTypesMatch, _ := regexp.Compile(" return this\\.(call|query)<(.+), (.+)>")

// Collect name -> fullName of transactions in this file.
// Collect TxTypeName -> fullName of transaction params and results in the file.
for lineIdx := 0; lineIdx < len(text); lineIdx += 1 {
methodMatch := regMethodMatch.FindStringSubmatch(text[lineIdx])
if len(methodMatch) > 0 {
fullNameSplit := strings.Split(methodMatch[1], ".")
_, found := txs[methodMatch[1]]
if !found {
TypeScriptWarnings = append(TypeScriptWarnings, fmt.Errorf("unknown method %s in file %s:%d", methodMatch[1], p.filename, lineIdx+1))
}
p.txs[fullNameSplit[1]] = methodMatch[1]
}

callQueryMatch := regCallQueryMatch.FindStringSubmatch(text[lineIdx])
if len(callQueryMatch) == 3 {
fullName, valid := p.txs[callQueryMatch[2]]
if !valid {
TypeScriptWarnings = append(TypeScriptWarnings, fmt.Errorf("implementation of %s not defined as method in the beginning of %s", callQueryMatch[2], p.filename))
continue
}
if _, valid = txs[fullName]; !valid {
continue
}

txTypesMatch := regTxTypesMatch.FindStringSubmatch(text[lineIdx+1])
if len(txTypesMatch) == 4 {
if strings.HasPrefix(txTypesMatch[2], "types.") {
name := strings.TrimPrefix(txTypesMatch[2], "types.")
p.txParams[name] = fullName
}
if strings.HasPrefix(txTypesMatch[3], "types.") {
name := strings.TrimPrefix(txTypesMatch[3], "types.")
p.txResults[name] = fullName
}
}

_, lineFrom := findComment(text, lineIdx, " ")
lineTo, err := findEndBlock(text, lineIdx, " ", fullName)
if err != nil {
return err
}
txs[fullName].Ref[types.TypeScript] = types.Snippet{
Path: p.filename,
LineFrom: lineFrom,
LineTo: lineTo,
}
}
}

// Open types.ts of the same module and collect parameters and result snippets.
if len(p.txs) > 0 {
if err := p.populateParamsResultRefs(txs); err != nil {
return err
}
}

return nil
}

// populateParamsResultRefs opens types.ts file, finds corresponding parameters and results snippets
// for the provided transactions and populates the refs of global transactions.
func (p *TypeScriptParser) populateParamsResultRefs(txs map[string]types.Tx) error {
typesPath := filepath.Join(filepath.Dir(p.filename), "types.ts")
text, err := readFile(typesPath)
if err != nil {
return err
}

regTypeMatch, _ := regexp.Compile("export interface ([a-zA-Z]+)")
for lineIdx := 0; lineIdx < len(text); lineIdx += 1 {
typeMatch := regTypeMatch.FindStringSubmatch(text[lineIdx])
if len(typeMatch) > 0 {
_, lineFrom := findComment(text, lineIdx, "")
lineTo, err := findEndBlock(text, lineIdx, "", typeMatch[1])
if err != nil {
return err
}

snippet := types.Snippet{
Path: typesPath,
LineFrom: lineFrom,
LineTo: lineTo,
}
if fullName, valid := p.txParams[typeMatch[1]]; valid {
txs[fullName].ParametersRef[types.TypeScript] = snippet
} else if fullName, valid := p.txResults[typeMatch[1]]; valid {
txs[fullName].ResultRef[types.TypeScript] = snippet
}
}
}

return nil
}
60 changes: 60 additions & 0 deletions tools/extract-runtime-txs/parsers/typescript_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package parsers

import (
"fmt"
"testing"

"github.com/oasisprotocol/oasis-sdk/tools/extract-runtime-txs/types"

"github.com/stretchr/testify/require"
)

func TestPopulateTypeScriptRefs(t *testing.T) {
require := require.New(t)

txs := map[string]types.Tx{
"contracts.Upload": {
Module: "contracts",
Name: "Upload",
Comment: "",
Type: types.Call,
Ref: map[types.Lang]types.Snippet{},
Parameters: []types.Parameter{},
ParametersRef: make(map[types.Lang]types.Snippet),
Result: []types.Parameter{},
ResultRef: map[types.Lang]types.Snippet{},
},
}

tsParser := NewTypeScriptParser("../tests/typescript/contracts.ts")
err := tsParser.populateTransactionRefs(txs)
require.NoError(err)
fmt.Println(txs["contracts.Upload"].Ref)
require.Equal(
types.Snippet{
Path: "../tests/typescript/contracts.ts",
LineFrom: 50,
LineTo: 52,
},
txs["contracts.Upload"].Ref[types.TypeScript],
"check implementation reference from TypeScript source file",
)
require.Equal(
types.Snippet{
Path: "../tests/typescript/types.ts",
LineFrom: 474,
LineTo: 490,
},
txs["contracts.Upload"].ParametersRef[types.TypeScript],
"check parameters reference from TypeScript source file",
)
require.Equal(
types.Snippet{
Path: "../tests/typescript/types.ts",
LineFrom: 492,
LineTo: 500,
},
txs["contracts.Upload"].ResultRef[types.TypeScript],
"check result reference from TypeScript source file",
)
}
4 changes: 2 additions & 2 deletions tools/extract-runtime-txs/parsers/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,14 +31,14 @@ func readFile(filename string) ([]string, error) {
// extracts the content. If the comment is not found, the initial line is
// returned + 1.
func findComment(text []string, lineIdx int, indent string) (string, int) {
regMatchComment, _ := regexp.Compile(indent + "///? (.*)")
regMatchComment, _ := regexp.Compile(indent + "(///? |/\\*\\*| \\* | \\*/)(.*)")
comment := ""
for commentLine := lineIdx - 1; commentLine > 0; commentLine -= 1 {
commentMatch := regMatchComment.FindStringSubmatch(text[commentLine])
if len(commentMatch) == 0 {
break
}
comment = commentMatch[1] + " " + comment
comment = commentMatch[2] + " " + comment
lineIdx -= 1
}
comment = strings.TrimSpace(comment)
Expand Down
63 changes: 63 additions & 0 deletions tools/extract-runtime-txs/tests/typescript/contracts.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import * as transaction from './transaction';
import * as types from './types';
import * as wrapper from './wrapper';

/**
* Unique module name.
*/
export const MODULE_NAME = 'contracts';

export const ERR_INVALID_ARGUMENT_CODE = 1;
export const ERR_CODE_TOO_LARGE_CODE = 2;
export const ERR_CODE_MALFORMED_CODE = 3;
export const ERR_UNSUPPORTED_ABI_CODE = 4;
export const ERR_CODE_MISSING_REQUIRED_EXPORT_CODE = 5;
export const ERR_CODE_DECLARES_RESERVED_EXPORT_CODE = 6;
export const ERR_CODE_DECLARES_START_FUNCTION_CODE = 7;
export const ERR_CODE_DECLARES_TOO_MANY_MEMORIES_CODE = 8;
export const ERR_CODE_NOT_FOUND_CODE = 9;
export const ERR_INSTANCE_NOT_FOUND_CODE = 10;
export const ERR_MODULE_LOADING_FAILED_CODE = 11;
export const ERR_EXECUTION_FAILED_CODE = 12;
export const ERR_FORBIDDEN_CODE = 13;
export const ERR_UNSUPPORTED_CODE = 14;
export const ERR_INSUFFICIENT_CALLER_BALANCE_CODE = 15;
export const ERR_CALL_DEPTH_EXCEEDED_CODE = 16;
export const ERR_RESULT_TOO_LARGE_CODE = 17;
export const ERR_TOO_MANY_SUBCALLS_CODE = 18;
export const ERR_CODE_ALREADY_UPGRADED_CODE = 19;

// Callable methods.
export const METHOD_UPLOAD = 'contracts.Upload';
export const METHOD_INSTANTIATE = 'contracts.Instantiate';
export const METHOD_CALL = 'contracts.Call';
export const METHOD_UPGRADE = 'contracts.Upgrade';
// Queries.
export const METHOD_CODE = 'contracts.Code';
export const METHOD_INSTANCE = 'contracts.Instance';
export const METHOD_INSTANCE_STORAGE = 'contracts.InstanceStorage';
export const METHOD_PUBLIC_KEY = 'contracts.PublicKey';
export const METHOD_CUSTOM = 'contracts.Custom';

// Public key kind.
export const PUBLIC_KEY_KIND_TRANSACTION = 1;

export class Wrapper extends wrapper.Base {
constructor(runtimeID: Uint8Array) {
super(runtimeID);
}

callUpload() {
return this.call<types.ContractsUpload, types.ContractsUploadResult>(METHOD_UPLOAD);
}
}

/**
* Use this as a part of a {@link transaction.CallHandlers}.
*/
export type TransactionCallHandlers = {
[METHOD_UPLOAD]?: transaction.CallHandler<types.ContractsUpload>;
[METHOD_INSTANTIATE]?: transaction.CallHandler<types.ContractsInstantiate>;
[METHOD_CALL]?: transaction.CallHandler<types.ContractsCall>;
[METHOD_UPGRADE]?: transaction.CallHandler<types.ContractsUpgrade>;
};
Loading

0 comments on commit 470c885

Please sign in to comment.