Support checking syntax of generated Blueprint files
Adds a CheckBlueprintSyntax(...) method to check the syntax of a Blueprint file. Changes processModuleDef and newModule from being method on *Context to being standalone functions. That ensures that CheckBlueprintSyntax(...) does not need to take a context and so there is no chance that it can change its state.
This commit is contained in:
parent
301749801e
commit
2a2c58ef46
3 changed files with 139 additions and 10 deletions
16
context.go
16
context.go
|
@ -692,7 +692,7 @@ func (c *Context) ParseFileList(rootDir string, filePaths []string,
|
||||||
var scopedModuleFactories map[string]ModuleFactory
|
var scopedModuleFactories map[string]ModuleFactory
|
||||||
|
|
||||||
var addModule func(module *moduleInfo) []error
|
var addModule func(module *moduleInfo) []error
|
||||||
addModule = func(module *moduleInfo) ([]error) {
|
addModule = func(module *moduleInfo) []error {
|
||||||
// Run any load hooks immediately before it is sent to the moduleCh and is
|
// Run any load hooks immediately before it is sent to the moduleCh and is
|
||||||
// registered by name. This allows load hooks to set and/or modify any aspect
|
// registered by name. This allows load hooks to set and/or modify any aspect
|
||||||
// of the module (including names) using information that is not available when
|
// of the module (including names) using information that is not available when
|
||||||
|
@ -716,7 +716,7 @@ func (c *Context) ParseFileList(rootDir string, filePaths []string,
|
||||||
for _, def := range file.Defs {
|
for _, def := range file.Defs {
|
||||||
switch def := def.(type) {
|
switch def := def.(type) {
|
||||||
case *parser.Module:
|
case *parser.Module:
|
||||||
module, errs := c.processModuleDef(def, file.Name, scopedModuleFactories)
|
module, errs := processModuleDef(def, file.Name, c.moduleFactories, scopedModuleFactories, c.ignoreUnknownModuleTypes)
|
||||||
if len(errs) == 0 && module != nil {
|
if len(errs) == 0 && module != nil {
|
||||||
errs = addModule(module)
|
errs = addModule(module)
|
||||||
}
|
}
|
||||||
|
@ -1349,7 +1349,7 @@ func (c *Context) prettyPrintGroupVariants(group *moduleGroup) string {
|
||||||
return strings.Join(variants, "\n ")
|
return strings.Join(variants, "\n ")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Context) newModule(factory ModuleFactory) *moduleInfo {
|
func newModule(factory ModuleFactory) *moduleInfo {
|
||||||
logicModule, properties := factory()
|
logicModule, properties := factory()
|
||||||
|
|
||||||
module := &moduleInfo{
|
module := &moduleInfo{
|
||||||
|
@ -1362,15 +1362,15 @@ func (c *Context) newModule(factory ModuleFactory) *moduleInfo {
|
||||||
return module
|
return module
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Context) processModuleDef(moduleDef *parser.Module,
|
func processModuleDef(moduleDef *parser.Module,
|
||||||
relBlueprintsFile string, scopedModuleFactories map[string]ModuleFactory) (*moduleInfo, []error) {
|
relBlueprintsFile string, moduleFactories, scopedModuleFactories map[string]ModuleFactory, ignoreUnknownModuleTypes bool) (*moduleInfo, []error) {
|
||||||
|
|
||||||
factory, ok := c.moduleFactories[moduleDef.Type]
|
factory, ok := moduleFactories[moduleDef.Type]
|
||||||
if !ok && scopedModuleFactories != nil {
|
if !ok && scopedModuleFactories != nil {
|
||||||
factory, ok = scopedModuleFactories[moduleDef.Type]
|
factory, ok = scopedModuleFactories[moduleDef.Type]
|
||||||
}
|
}
|
||||||
if !ok {
|
if !ok {
|
||||||
if c.ignoreUnknownModuleTypes {
|
if ignoreUnknownModuleTypes {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1382,7 +1382,7 @@ func (c *Context) processModuleDef(moduleDef *parser.Module,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module := c.newModule(factory)
|
module := newModule(factory)
|
||||||
module.typeName = moduleDef.Type
|
module.typeName = moduleDef.Type
|
||||||
|
|
||||||
module.relBlueprintsFile = relBlueprintsFile
|
module.relBlueprintsFile = relBlueprintsFile
|
||||||
|
|
|
@ -17,9 +17,11 @@ package blueprint
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"text/scanner"
|
"text/scanner"
|
||||||
|
|
||||||
|
"github.com/google/blueprint/parser"
|
||||||
"github.com/google/blueprint/pathtools"
|
"github.com/google/blueprint/pathtools"
|
||||||
"github.com/google/blueprint/proptools"
|
"github.com/google/blueprint/proptools"
|
||||||
)
|
)
|
||||||
|
@ -988,7 +990,7 @@ func (mctx *mutatorContext) Rename(name string) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (mctx *mutatorContext) CreateModule(factory ModuleFactory, props ...interface{}) Module {
|
func (mctx *mutatorContext) CreateModule(factory ModuleFactory, props ...interface{}) Module {
|
||||||
module := mctx.context.newModule(factory)
|
module := newModule(factory)
|
||||||
|
|
||||||
module.relBlueprintsFile = mctx.module.relBlueprintsFile
|
module.relBlueprintsFile = mctx.module.relBlueprintsFile
|
||||||
module.pos = mctx.module.pos
|
module.pos = mctx.module.pos
|
||||||
|
@ -1035,7 +1037,7 @@ type LoadHookContext interface {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *loadHookContext) CreateModule(factory ModuleFactory, props ...interface{}) Module {
|
func (l *loadHookContext) CreateModule(factory ModuleFactory, props ...interface{}) Module {
|
||||||
module := l.context.newModule(factory)
|
module := newModule(factory)
|
||||||
|
|
||||||
module.relBlueprintsFile = l.module.relBlueprintsFile
|
module.relBlueprintsFile = l.module.relBlueprintsFile
|
||||||
module.pos = l.module.pos
|
module.pos = l.module.pos
|
||||||
|
@ -1123,3 +1125,44 @@ func runAndRemoveLoadHooks(ctx *Context, config interface{}, module *moduleInfo,
|
||||||
|
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check the syntax of a generated blueprint file.
|
||||||
|
//
|
||||||
|
// This is intended to perform a quick sanity check for generated blueprint
|
||||||
|
// code to ensure that it is syntactically correct, where syntactically correct
|
||||||
|
// means:
|
||||||
|
// * No variable definitions.
|
||||||
|
// * Valid module types.
|
||||||
|
// * Valid property names.
|
||||||
|
// * Valid values for the property type.
|
||||||
|
//
|
||||||
|
// It does not perform any semantic checking of properties, existence of referenced
|
||||||
|
// files, or dependencies.
|
||||||
|
//
|
||||||
|
// At a low level it:
|
||||||
|
// * Parses the contents.
|
||||||
|
// * Invokes relevant factory to create Module instances.
|
||||||
|
// * Unpacks the properties into the Module.
|
||||||
|
// * Does not invoke load hooks or any mutators.
|
||||||
|
//
|
||||||
|
// The filename is only used for reporting errors.
|
||||||
|
func CheckBlueprintSyntax(moduleFactories map[string]ModuleFactory, filename string, contents string) []error {
|
||||||
|
scope := parser.NewScope(nil)
|
||||||
|
file, errs := parser.Parse(filename, strings.NewReader(contents), scope)
|
||||||
|
if len(errs) != 0 {
|
||||||
|
return errs
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, def := range file.Defs {
|
||||||
|
switch def := def.(type) {
|
||||||
|
case *parser.Module:
|
||||||
|
_, moduleErrs := processModuleDef(def, filename, moduleFactories, nil, false)
|
||||||
|
errs = append(errs, moduleErrs...)
|
||||||
|
|
||||||
|
default:
|
||||||
|
panic(fmt.Errorf("unknown definition type: %T", def))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return errs
|
||||||
|
}
|
||||||
|
|
|
@ -195,3 +195,89 @@ func TestAliases(t *testing.T) {
|
||||||
"\n 1:a, 2:a\n 1:a, 2:b\n 1:b, 2:a\n 1:b, 2:b")
|
"\n 1:a, 2:a\n 1:a, 2:b\n 1:b, 2:a\n 1:b, 2:b")
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func expectedErrors(t *testing.T, errs []error, expectedMessages ...string) {
|
||||||
|
t.Helper()
|
||||||
|
if len(errs) != len(expectedMessages) {
|
||||||
|
t.Errorf("expected %d error, found: %q", len(expectedMessages), errs)
|
||||||
|
} else {
|
||||||
|
for i, expected := range expectedMessages {
|
||||||
|
err := errs[i]
|
||||||
|
if err.Error() != expected {
|
||||||
|
t.Errorf("expected error %q found %q", expected, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCheckBlueprintSyntax(t *testing.T) {
|
||||||
|
factories := map[string]ModuleFactory{
|
||||||
|
"test": newModuleCtxTestModule,
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Run("valid", func(t *testing.T) {
|
||||||
|
errs := CheckBlueprintSyntax(factories, "path/Blueprint", `
|
||||||
|
test {
|
||||||
|
name: "test",
|
||||||
|
}
|
||||||
|
`)
|
||||||
|
expectedErrors(t, errs)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("syntax error", func(t *testing.T) {
|
||||||
|
errs := CheckBlueprintSyntax(factories, "path/Blueprint", `
|
||||||
|
test {
|
||||||
|
name: "test",
|
||||||
|
|
||||||
|
`)
|
||||||
|
|
||||||
|
expectedErrors(t, errs, `path/Blueprint:5:1: expected "}", found EOF`)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("unknown module type", func(t *testing.T) {
|
||||||
|
errs := CheckBlueprintSyntax(factories, "path/Blueprint", `
|
||||||
|
test2 {
|
||||||
|
name: "test",
|
||||||
|
}
|
||||||
|
`)
|
||||||
|
|
||||||
|
expectedErrors(t, errs, `path/Blueprint:2:1: unrecognized module type "test2"`)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("unknown property name", func(t *testing.T) {
|
||||||
|
errs := CheckBlueprintSyntax(factories, "path/Blueprint", `
|
||||||
|
test {
|
||||||
|
nam: "test",
|
||||||
|
}
|
||||||
|
`)
|
||||||
|
|
||||||
|
expectedErrors(t, errs, `path/Blueprint:3:5: unrecognized property "nam"`)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("invalid property type", func(t *testing.T) {
|
||||||
|
errs := CheckBlueprintSyntax(factories, "path/Blueprint", `
|
||||||
|
test {
|
||||||
|
name: false,
|
||||||
|
}
|
||||||
|
`)
|
||||||
|
|
||||||
|
expectedErrors(t, errs, `path/Blueprint:3:8: can't assign bool value to string property "name"`)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("multiple failures", func(t *testing.T) {
|
||||||
|
errs := CheckBlueprintSyntax(factories, "path/Blueprint", `
|
||||||
|
test {
|
||||||
|
name: false,
|
||||||
|
}
|
||||||
|
|
||||||
|
test2 {
|
||||||
|
name: false,
|
||||||
|
}
|
||||||
|
`)
|
||||||
|
|
||||||
|
expectedErrors(t, errs,
|
||||||
|
`path/Blueprint:3:8: can't assign bool value to string property "name"`,
|
||||||
|
`path/Blueprint:6:1: unrecognized module type "test2"`,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue