mirror of
https://github.com/wavetermdev/wails.git
synced 2026-08-05 13:53:43 -07:00
[v3] Fix binding generator output and import paths (#3334)
* Fix relative import path computation * Fix models output path * Add option to generate bindings using bundled runtime * Update binding example * Fix testdata * Update changelog --------- Co-authored-by: Lea Anthony <lea.anthony@gmail.com>
This commit is contained in:
co-authored by
Lea Anthony
parent
f0986a6441
commit
45b2681dfc
@@ -30,6 +30,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
- Expose the `WebviewWindow.IsFocused` method on the `Window` interface by [@fbbdev](https://github.com/fbbdev) in [#3295](https://github.com/wailsapp/wails/pull/3295)
|
||||
- Support multiple space-separated trigger events in the WML system by [@fbbdev](https://github.com/fbbdev) in [#3295](https://github.com/wailsapp/wails/pull/3295)
|
||||
- Add ESM exports from the bundled JS runtime script by [@fbbdev](https://github.com/fbbdev) in [#3295](https://github.com/wailsapp/wails/pull/3295)
|
||||
- Add binding generator flag for using the bundled JS runtime script instead of the npm package by [@fbbdev](https://github.com/fbbdev) in [#3334](https://github.com/wailsapp/wails/pull/3334)
|
||||
|
||||
### Fixed
|
||||
|
||||
@@ -51,6 +52,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
- Correctly compute `startURL` across multiple `GetStartURL` invocations when `FRONTEND_DEVSERVER_URL` is present. [#3299](https://github.com/wailsapp/wails/pull/3299)
|
||||
- Fix the JS type of the `Screen` struct to match its Go counterpart by [@fbbdev](https://github.com/fbbdev) in [#3295](https://github.com/wailsapp/wails/pull/3295)
|
||||
- Fix the `WML.Reload` method to ensure proper cleanup of registered event listeners by [@fbbdev](https://github.com/fbbdev) in [#3295](https://github.com/wailsapp/wails/pull/3295)
|
||||
- Fix the output path and extension of model files produced by the binding generator by [@fbbdev](https://github.com/fbbdev) in [#3334](https://github.com/wailsapp/wails/pull/3334)
|
||||
- Fix the import paths of model files in JS code produced by the binding generator by [@fbbdev](https://github.com/fbbdev) in [#3334](https://github.com/wailsapp/wails/pull/3334)
|
||||
|
||||
### Changed
|
||||
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
package main
|
||||
|
||||
type Person struct {
|
||||
name string
|
||||
}
|
||||
import "github.com/wailsapp/wails/v3/examples/binding/data"
|
||||
|
||||
// GreetService is a service that greets people
|
||||
type GreetService struct {
|
||||
@@ -14,6 +12,6 @@ func (*GreetService) Greet(name string) string {
|
||||
}
|
||||
|
||||
// GreetPerson greets a person
|
||||
func (*GreetService) GreetPerson(person Person) string {
|
||||
return "Hello " + person.name
|
||||
func (*GreetService) GreetPerson(person data.Person) string {
|
||||
return "Hello " + person.Name
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
This example demonstrates how to generate bindings for your application.
|
||||
|
||||
To generate bindings, run `wails3 generate bindings -d assets` in this directory.
|
||||
To generate bindings, run `wails3 generate bindings -b -d assets/bindings` in this directory.
|
||||
|
||||
See more options by running `wails3 generate bindings --help`.
|
||||
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
// @ts-check
|
||||
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
|
||||
// This file is automatically generated. DO NOT EDIT
|
||||
|
||||
// Person holds someone's most important attributes
|
||||
export const Person = class {
|
||||
/**
|
||||
* Creates a new Person instance.
|
||||
* @constructor
|
||||
* @param {Object} source - The source object to create the Person.
|
||||
* @param {string} source.Name
|
||||
*/
|
||||
constructor(source = {}) {
|
||||
const { name = "" } = source;
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new Person instance from a string or object.
|
||||
* @param {string|object} source - The source data to create a Person instance from.
|
||||
* @returns {Person} A new Person instance.
|
||||
*/
|
||||
static createFrom(source) {
|
||||
let parsedSource = typeof source === 'string' ? JSON.parse(source) : source;
|
||||
return new Person(parsedSource);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
// @ts-check
|
||||
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
|
||||
// This file is automatically generated. DO NOT EDIT
|
||||
|
||||
import {Call} from '/wails/runtime.js';
|
||||
/**
|
||||
* @typedef {import('../data/models').Person} dataPerson
|
||||
*/
|
||||
|
||||
/**
|
||||
* Greet greets a person
|
||||
* @function Greet
|
||||
* @param name {string}
|
||||
* @returns {Promise<string>}
|
||||
**/
|
||||
export async function Greet(name) {
|
||||
return Call.ByName("main.GreetService.Greet", ...Array.prototype.slice.call(arguments, 0));
|
||||
}
|
||||
|
||||
/**
|
||||
* GreetPerson greets a person
|
||||
* @function GreetPerson
|
||||
* @param person {dataPerson}
|
||||
* @returns {Promise<string>}
|
||||
**/
|
||||
export async function GreetPerson(person) {
|
||||
return Call.ByName("main.GreetService.GreetPerson", ...Array.prototype.slice.call(arguments, 0));
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
// @ts-check
|
||||
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
|
||||
// This file is automatically generated. DO NOT EDIT
|
||||
|
||||
|
||||
window.go = window.go || {};
|
||||
window.go.main = {
|
||||
GreetService: {
|
||||
|
||||
/**
|
||||
* GreetService.Greet
|
||||
* Greet greets a person
|
||||
* @param name {string}
|
||||
* @returns {Promise<string>}
|
||||
**/
|
||||
Greet: function(name) { return wails.Call.ByID(1411160069, ...Array.prototype.slice.call(arguments, 0)); },
|
||||
|
||||
/**
|
||||
* GreetService.GreetPerson
|
||||
* GreetPerson greets a person
|
||||
* @param person {main.Person}
|
||||
* @returns {Promise<string>}
|
||||
**/
|
||||
GreetPerson: function(person) { return wails.Call.ByID(4021313248, ...Array.prototype.slice.call(arguments, 0)); },
|
||||
},
|
||||
};
|
||||
@@ -78,18 +78,17 @@
|
||||
<div class="result" id="result">Please enter your name below 👇</div>
|
||||
<div class="input-box" id="input">
|
||||
<input autofocus autocomplete="off" class="input" id="name" type="text"/>
|
||||
<button class="btn" onclick="greet()">Greet</button>
|
||||
<button class="btn" id="greet-btn">Greet</button>
|
||||
</div>
|
||||
<script type="module" src='/wails/runtime.js'></script>
|
||||
<script src='bindings_main.js'></script>
|
||||
<script>
|
||||
let resultText = "";
|
||||
let name = "";
|
||||
<script type="module">
|
||||
import * as GreetService from "./bindings/main/GreetService.js";
|
||||
|
||||
function greet() {
|
||||
window.go.main.GreetService.Greet(document.getElementById("name").value).then((result) =>
|
||||
document.getElementById("result").innerHTML = result);
|
||||
}
|
||||
async function greet() {
|
||||
document.getElementById("result").innerHTML =
|
||||
await GreetService.Greet(document.getElementById("name").value);
|
||||
}
|
||||
|
||||
document.getElementById("greet-btn").onclick = greet;
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -1,27 +0,0 @@
|
||||
// @ts-check
|
||||
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
|
||||
// This file is automatically generated. DO NOT EDIT
|
||||
|
||||
/**
|
||||
* @typedef {import('./models').main.Person} mainPerson
|
||||
*/
|
||||
|
||||
export const GreetService = {
|
||||
|
||||
/**
|
||||
* GreetService.Greet
|
||||
* Greet greets a person
|
||||
* @param name {string}
|
||||
* @returns {Promise<string>}
|
||||
**/
|
||||
Greet: function(name) { return wails.CallByID(1411160069, ...Array.prototype.slice.call(arguments, 0)); },
|
||||
|
||||
/**
|
||||
* GreetService.GreetPerson
|
||||
* GreetPerson greets a person
|
||||
* @param person {mainPerson}
|
||||
* @returns {Promise<string>}
|
||||
**/
|
||||
GreetPerson: function(person) { return wails.CallByID(4021313248, ...Array.prototype.slice.call(arguments, 0)); },
|
||||
};
|
||||
|
||||
@@ -1,23 +0,0 @@
|
||||
// @ts-check
|
||||
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
|
||||
// This file is automatically generated. DO NOT EDIT
|
||||
|
||||
export namespace main {
|
||||
|
||||
|
||||
export class Person {
|
||||
name: string; // Warning: this is unexported in the Go struct.
|
||||
|
||||
constructor(source: Partial<Person> = {}) {
|
||||
const { name = "" } = source;
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
static createFrom(source: string | object = {}): Person {
|
||||
let parsedSource = typeof source === 'string' ? JSON.parse(source) : source;
|
||||
return new Person(parsedSource as Partial<Person>);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package data
|
||||
|
||||
// Person holds someone's most important attributes
|
||||
type Person struct {
|
||||
// Name is the person's name
|
||||
Name string `json:"name"`
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
// @ts-check
|
||||
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
|
||||
// This file is automatically generated. DO NOT EDIT
|
||||
|
||||
export namespace main {
|
||||
|
||||
export class Person {
|
||||
name: string; // Warning: this is unexported in the Go struct.
|
||||
|
||||
static createFrom(source: any = {}) {
|
||||
return new Person(source);
|
||||
}
|
||||
|
||||
constructor(source: any = {}) {
|
||||
if ('string' === typeof source) {
|
||||
source = JSON.parse(source);
|
||||
}
|
||||
|
||||
this.name = source['name'];
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,13 +1,14 @@
|
||||
package flags
|
||||
|
||||
type GenerateBindingsOptions struct {
|
||||
Silent bool `name:"silent" description:"Silent mode"`
|
||||
ModelsFilename string `name:"m" description:"The filename for the models file" default:"models.ts"`
|
||||
TS bool `name:"ts" description:"Generate Typescript bindings"`
|
||||
TSPrefix string `description:"The prefix for the typescript names" default:""`
|
||||
TSSuffix string `description:"The postfix for the typescript names" default:""`
|
||||
UseInterfaces bool `name:"i" description:"Use interfaces instead of classes"`
|
||||
ProjectDirectory string `name:"p" description:"The project directory" default:"."`
|
||||
UseIDs bool `name:"ids" description:"Use IDs instead of names in the binding calls"`
|
||||
OutputDirectory string `name:"d" description:"The output directory" default:"frontend/bindings"`
|
||||
Silent bool `name:"silent" description:"Silent mode"`
|
||||
ModelsFilename string `name:"m" description:"The filename for the models file, excluding the extension" default:"models"`
|
||||
TS bool `name:"ts" description:"Generate Typescript bindings"`
|
||||
TSPrefix string `description:"The prefix for the typescript names" default:""`
|
||||
TSSuffix string `description:"The postfix for the typescript names" default:""`
|
||||
UseInterfaces bool `name:"i" description:"Use interfaces instead of classes"`
|
||||
UseBundledRuntime bool `name:"b" description:"Use the bundled runtime instead of importing the npm package"`
|
||||
ProjectDirectory string `name:"p" description:"The project directory" default:"."`
|
||||
UseIDs bool `name:"ids" description:"Use IDs instead of names in the binding calls"`
|
||||
OutputDirectory string `name:"d" description:"The output directory" default:"frontend/bindings"`
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@ const headerTypescript = `// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â M
|
||||
`
|
||||
|
||||
const bindingTemplate = `
|
||||
/**Comments
|
||||
/**Comments
|
||||
* @function {{methodName}}* @param names {string}
|
||||
* @returns {Promise<string>}
|
||||
**/
|
||||
@@ -345,7 +345,7 @@ func isContext(input *Parameter) bool {
|
||||
return input.Type.Package == "context" && input.Type.Name == "Context"
|
||||
}
|
||||
|
||||
func (p *Project) GenerateBindings(bindings map[string]map[string][]*BoundMethod, useIDs bool, useTypescript bool) map[string]map[string]string {
|
||||
func (p *Project) GenerateBindings(bindings map[string]map[string][]*BoundMethod, modelsFilename string, useIDs bool, useTypescript bool, useBundledRuntime bool) map[string]map[string]string {
|
||||
|
||||
var result = make(map[string]map[string]string)
|
||||
|
||||
@@ -374,7 +374,11 @@ func (p *Project) GenerateBindings(bindings map[string]map[string][]*BoundMethod
|
||||
var models []string
|
||||
var mainImports = ""
|
||||
if len(methods) > 0 {
|
||||
mainImports = "import {Call} from '@wailsio/runtime';\n"
|
||||
if useBundledRuntime {
|
||||
mainImports = "import {Call} from '/wails/runtime.js';\n"
|
||||
} else {
|
||||
mainImports = "import {Call} from '@wailsio/runtime';\n"
|
||||
}
|
||||
}
|
||||
for _, method := range methods {
|
||||
if useTypescript {
|
||||
@@ -405,7 +409,7 @@ func (p *Project) GenerateBindings(bindings map[string]map[string][]*BoundMethod
|
||||
sort.Strings(namespacedStructNames)
|
||||
for _, thisStructName := range namespacedStructNames {
|
||||
structInfo := namespacedStruct[thisStructName]
|
||||
typedefs += " * @typedef {import('" + relativePackageDir + "/models')." + thisStructName + "} " + namePrefix + structInfo.Name + "\n"
|
||||
typedefs += " * @typedef {import('" + relativePackageDir + "/" + modelsFilename + "')." + thisStructName + "} " + namePrefix + structInfo.Name + "\n"
|
||||
}
|
||||
}
|
||||
typedefs += " */\n"
|
||||
@@ -429,7 +433,7 @@ func (p *Project) GenerateBindings(bindings map[string]map[string][]*BoundMethod
|
||||
if namePrefix != "" {
|
||||
imports += "import {" + thisStructName + " as " + namePrefix + structInfo.Name + "} from '" + relativePackageDir + "/models';\n"
|
||||
} else {
|
||||
imports += "import {" + thisStructName + "} from '" + relativePackageDir + "/models';\n"
|
||||
imports += "import {" + thisStructName + "} from '" + relativePackageDir + "/" + modelsFilename + "';\n"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -684,7 +684,7 @@ func TestGenerateBindings(t *testing.T) {
|
||||
project.outputDirectory = "frontend/bindings"
|
||||
|
||||
// Generate Bindings
|
||||
got := project.GenerateBindings(project.BoundMethods, tt.useIDs, tt.useTypescript)
|
||||
got := project.GenerateBindings(project.BoundMethods, "models", tt.useIDs, tt.useTypescript, false)
|
||||
|
||||
for dirName, structDetails := range got {
|
||||
// iterate the struct names in structDetails
|
||||
|
||||
@@ -3,11 +3,12 @@ package parser
|
||||
import (
|
||||
"bytes"
|
||||
"embed"
|
||||
"github.com/wailsapp/wails/v3/internal/flags"
|
||||
"io"
|
||||
"sort"
|
||||
"strings"
|
||||
"text/template"
|
||||
|
||||
"github.com/wailsapp/wails/v3/internal/flags"
|
||||
)
|
||||
|
||||
//go:embed templates
|
||||
@@ -36,7 +37,7 @@ func (p *Project) GenerateModel(wr io.Writer, def *ModelDefinitions, options *fl
|
||||
|
||||
tmpl, err := template.New(templateName).ParseFS(templates, "templates/"+templateName)
|
||||
if err != nil {
|
||||
println("Unable to create class template: " + err.Error())
|
||||
println("Unable to initialize model template: " + err.Error())
|
||||
return err
|
||||
}
|
||||
|
||||
|
||||
@@ -351,27 +351,32 @@ func GenerateBindingsAndModels(options *flags.GenerateBindingsOptions) (*Project
|
||||
if err != nil {
|
||||
return p, err
|
||||
}
|
||||
|
||||
p.outputDirectory = options.OutputDirectory
|
||||
|
||||
for _, pkg := range p.BoundMethods {
|
||||
for _, boundMethods := range pkg {
|
||||
p.Stats.NumMethods += len(boundMethods)
|
||||
}
|
||||
}
|
||||
generatedMethods := p.GenerateBindings(p.BoundMethods, options.UseIDs, options.TS)
|
||||
for pkg, structs := range generatedMethods {
|
||||
|
||||
generatedMethods := p.GenerateBindings(p.BoundMethods, options.ModelsFilename, options.UseIDs, options.TS, options.UseBundledRuntime)
|
||||
for pkgDir, structs := range generatedMethods {
|
||||
// Write the directory
|
||||
err = os.MkdirAll(filepath.Join(options.OutputDirectory, pkg), 0755)
|
||||
err = os.MkdirAll(filepath.Join(options.OutputDirectory, pkgDir), 0755)
|
||||
if err != nil && !os.IsExist(err) {
|
||||
return p, err
|
||||
}
|
||||
// Write the files
|
||||
for structName, text := range structs {
|
||||
p.Stats.NumStructs++
|
||||
filename := structName + ".js"
|
||||
var filename string
|
||||
if options.TS {
|
||||
filename = structName + ".ts"
|
||||
} else {
|
||||
filename = structName + ".js"
|
||||
}
|
||||
err = os.WriteFile(filepath.Join(options.OutputDirectory, pkg, filename), []byte(text), 0644)
|
||||
err = os.WriteFile(filepath.Join(options.OutputDirectory, pkgDir, filename), []byte(text), 0644)
|
||||
if err != nil {
|
||||
return p, err
|
||||
}
|
||||
@@ -387,12 +392,20 @@ func GenerateBindingsAndModels(options *flags.GenerateBindingsOptions) (*Project
|
||||
if err != nil {
|
||||
return p, err
|
||||
}
|
||||
for pkg, text := range generatedModels {
|
||||
// Get directory for package
|
||||
pkgInfo := p.packageCache[pkg]
|
||||
relativePackageDir := p.RelativeBindingsDir(pkgInfo, pkgInfo)
|
||||
for pkgDir, text := range generatedModels {
|
||||
// Write the directory
|
||||
err = os.WriteFile(filepath.Join(options.OutputDirectory, relativePackageDir, options.ModelsFilename), []byte(text), 0644)
|
||||
err = os.MkdirAll(filepath.Join(options.OutputDirectory, pkgDir), 0755)
|
||||
if err != nil && !os.IsExist(err) {
|
||||
return p, err
|
||||
}
|
||||
// Write the file
|
||||
var filename string
|
||||
if options.TS {
|
||||
filename = options.ModelsFilename + ".ts"
|
||||
} else {
|
||||
filename = options.ModelsFilename + ".js"
|
||||
}
|
||||
err = os.WriteFile(filepath.Join(options.OutputDirectory, pkgDir, filename), []byte(text), 0644)
|
||||
}
|
||||
if err != nil {
|
||||
return p, err
|
||||
@@ -1175,25 +1188,35 @@ func (p *Project) parseTypes(pkgs map[string]*ParsedPackage) {
|
||||
}
|
||||
|
||||
func (p *Project) RelativeBindingsDir(dir *ParsedPackage, dir2 *ParsedPackage) string {
|
||||
|
||||
if dir.Dir == dir2.Dir {
|
||||
return "."
|
||||
}
|
||||
|
||||
// Calculate the relative path from the bindings directory to the package directory
|
||||
absoluteSourceDir := dir.Dir
|
||||
if absoluteSourceDir == p.Path {
|
||||
// Calculate the relative path from the bindings directory of dir to that of dir2
|
||||
var (
|
||||
absoluteSourceDir string
|
||||
absoluteTargetDir string
|
||||
)
|
||||
|
||||
if dir.Dir == p.Path {
|
||||
absoluteSourceDir = filepath.Join(p.Path, p.outputDirectory, "main")
|
||||
} else {
|
||||
absoluteSourceDir = dir.Dir
|
||||
relativeSourceDir := strings.TrimPrefix(dir.Dir, p.Path)
|
||||
absoluteSourceDir = filepath.Join(p.Path, p.outputDirectory, relativeSourceDir)
|
||||
}
|
||||
targetRelativeDir := strings.TrimPrefix(dir2.Dir, p.Path)
|
||||
targetBindingsDir := filepath.Join(p.Path, p.outputDirectory, targetRelativeDir)
|
||||
// Calculate the relative path from the source directory to the target directory
|
||||
relativePath, err := filepath.Rel(absoluteSourceDir, targetBindingsDir)
|
||||
|
||||
if dir2.Dir == p.Path {
|
||||
absoluteTargetDir = filepath.Join(p.Path, p.outputDirectory, "main")
|
||||
} else {
|
||||
relativeTargetDir := strings.TrimPrefix(dir2.Dir, p.Path)
|
||||
absoluteTargetDir = filepath.Join(p.Path, p.outputDirectory, relativeTargetDir)
|
||||
}
|
||||
|
||||
relativePath, err := filepath.Rel(absoluteSourceDir, absoluteTargetDir)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return filepath.ToSlash(relativePath)
|
||||
}
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ import {Call} from '@wailsio/runtime';
|
||||
*/
|
||||
|
||||
/**
|
||||
* Greet does XYZ
|
||||
* Greet does XYZ
|
||||
* @function Greet
|
||||
* @param name {string}
|
||||
* @param title {Title}
|
||||
@@ -20,7 +20,7 @@ export async function Greet(name, title) {
|
||||
}
|
||||
|
||||
/**
|
||||
* NewPerson creates a new person
|
||||
* NewPerson creates a new person
|
||||
* @function NewPerson
|
||||
* @param name {string}
|
||||
* @returns {Promise<Person>}
|
||||
|
||||
+2
-2
@@ -8,7 +8,7 @@
|
||||
*/
|
||||
|
||||
/**
|
||||
* Greet does XYZ
|
||||
* Greet does XYZ
|
||||
* @function Greet
|
||||
* @param name {string}
|
||||
* @param title {Title}
|
||||
@@ -19,7 +19,7 @@ export async function Greet(name, title) {
|
||||
}
|
||||
|
||||
/**
|
||||
* NewPerson creates a new person
|
||||
* NewPerson creates a new person
|
||||
* @function NewPerson
|
||||
* @param name {string}
|
||||
* @returns {Promise<Person>}
|
||||
|
||||
Vendored
+1
-1
@@ -8,7 +8,7 @@ import {Call} from '@wailsio/runtime';
|
||||
*/
|
||||
|
||||
/**
|
||||
* Greet does XYZ
|
||||
* Greet does XYZ
|
||||
* @function Greet
|
||||
* @param name {string}
|
||||
* @param title {servicesTitle}
|
||||
|
||||
Vendored
+1
-1
@@ -8,7 +8,7 @@ import {Call} from '@wailsio/runtime';
|
||||
*/
|
||||
|
||||
/**
|
||||
* Greet does XYZ
|
||||
* Greet does XYZ
|
||||
* @function Greet
|
||||
* @param name {string}
|
||||
* @param title {servicesTitle}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user