diff --git a/dslgen/dsl_sources.go b/dslgen/dsl_sources.go new file mode 100644 index 00000000..54c500f4 --- /dev/null +++ b/dslgen/dsl_sources.go @@ -0,0 +1,3 @@ +package dslgen + +var Fluent = []byte("package fluent\n\n// Matcher is a main API group-level entry point.\n// It's used to define and configure the group rules.\n// It also represents a map of all rule-local variables.\ntype Matcher map[string]Var\n\n// Import loads given package path into a rule group imports table.\n//\n// That table is used during the rules compilation.\n//\n// The table has the following effect on the rules:\n//\t* For type expressions, it's used to resolve the\n//\t full package paths of qualified types, like `foo.Bar`.\n//\t If Import(`a/b/foo`) is called, `foo.Bar` will match\n//\t `a/b/foo.Bar` type during the pattern execution.\nfunc (m Matcher) Import(pkgPath string) {}\n\n// Match specifies a set of patterns that match a rule being defined.\n// Pattern matching succeeds if at least 1 pattern matches.\n//\n// If none of the given patterns matched, rule execution stops.\nfunc (m Matcher) Match(pattern string, alternatives ...string) Matcher {\n\treturn m\n}\n\n// Where applies additional constraint to a match.\n// If a given cond is not satisfied, a match is rejected and\n// rule execution stops.\nfunc (m Matcher) Where(cond bool) Matcher {\n\treturn m\n}\n\n// Report prints a message if associated rule match is successful.\n//\n// A message is a string that can contain interpolated expressions.\n// For every matched variable it's possible to interpolate\n// their printed representation into the message text with $.\n// An entire match can be addressed with $$.\nfunc (m Matcher) Report(message string) Matcher {\n\treturn m\n}\n\n// Suggest assigns a quickfix suggestion for the matched code.\nfunc (m Matcher) Suggest(suggestion string) Matcher {\n\treturn m\n}\n\n// At binds the reported node to a named submatch.\n// If no explicit location is given, the outermost node ($$) is used.\nfunc (m Matcher) At(v Var) Matcher {\n\treturn m\n}\n\n// Var is a pattern variable that describes a named submatch.\ntype Var struct {\n\t// Pure reports whether expr matched by var is side-effect-free.\n\tPure bool\n\n\t// Const reports whether expr matched by var is a constant value.\n\tConst bool\n\n\t// Addressable reports whether the corresponding expression is addressable.\n\t// See https://golang.org/ref/spec#Address_operators.\n\tAddressable bool\n\n\t// Type is a type of a matched expr.\n\tType ExprType\n}\n\n// ExprType describes a type of a matcher expr.\ntype ExprType struct {\n\t// Size represents expression type size in bytes.\n\tSize int\n}\n\n// AssignableTo reports whether a type is assign-compatible with a given type.\n// See https://golang.org/pkg/go/types/#AssignableTo.\nfunc (ExprType) AssignableTo(typ string) bool { return boolResult }\n\n// ConvertibleTo reports whether a type is conversible to a given type.\n// See https://golang.org/pkg/go/types/#ConvertibleTo.\nfunc (ExprType) ConvertibleTo(typ string) bool { return boolResult }\n\n// Implements reports whether a type implements a given interface.\n// See https://golang.org/pkg/go/types/#Implements.\nfunc (ExprType) Implements(typ string) bool { return boolResult }\n\n// Is reports whether a type is identical to a given type.\nfunc (ExprType) Is(typ string) bool { return boolResult }\n\n\n\nvar boolResult bool\n\n") diff --git a/dslgen/dslgen.go b/dslgen/dslgen.go new file mode 100644 index 00000000..a2269b2e --- /dev/null +++ b/dslgen/dslgen.go @@ -0,0 +1,53 @@ +// +build generate + +package main + +import ( + "bytes" + "fmt" + "io/ioutil" + "os" + "path/filepath" +) + +func main() { + // See #23. + + data, err := dirToBytes("../dsl/fluent") + if err != nil { + panic(err) + } + + f, err := os.Create("./dsl_sources.go") + if err != nil { + panic(err) + } + defer f.Close() + + fmt.Fprintf(f, `package dslgen + +var Fluent = []byte(%q) +`, string(data)) +} + +func dirToBytes(dir string) ([]byte, error) { + files, err := ioutil.ReadDir(dir) + if err != nil { + return nil, err + } + + var buf bytes.Buffer + for i, f := range files { + data, err := ioutil.ReadFile(filepath.Join(dir, f.Name())) + if err != nil { + return nil, err + } + if i != 0 { + newline := bytes.IndexByte(data, '\n') + data = data[newline:] + } + buf.Write(data) + buf.WriteByte('\n') + } + return buf.Bytes(), nil +} diff --git a/ruleguard/dsl_importer.go b/ruleguard/dsl_importer.go new file mode 100644 index 00000000..c566578d --- /dev/null +++ b/ruleguard/dsl_importer.go @@ -0,0 +1,40 @@ +package ruleguard + +import ( + "go/ast" + "go/importer" + "go/parser" + "go/token" + "go/types" + + "github.com/quasilyte/go-ruleguard/dslgen" +) + +type dslImporter struct { + fallback types.Importer +} + +func newDSLImporter() *dslImporter { + return &dslImporter{fallback: importer.Default()} +} + +func (i *dslImporter) Import(path string) (*types.Package, error) { + switch path { + case "github.com/quasilyte/go-ruleguard/dsl/fluent": + return i.importDSL(path, dslgen.Fluent) + + default: + return i.fallback.Import(path) + } +} + +func (i *dslImporter) importDSL(path string, src []byte) (*types.Package, error) { + fset := token.NewFileSet() + f, err := parser.ParseFile(fset, "dsl.go", src, 0) + if err != nil { + return nil, err + } + var typecheker types.Config + var info types.Info + return typecheker.Check(path, fset, []*ast.File{f}, &info) +} diff --git a/ruleguard/parser.go b/ruleguard/parser.go index c5c176bd..f5b93679 100644 --- a/ruleguard/parser.go +++ b/ruleguard/parser.go @@ -22,6 +22,7 @@ type rulesParser struct { types *types.Info itab *typematch.ImportsTab + dslImporter types.Importer stdImporter types.Importer // TODO(quasilyte): share importer with gogrep? srcImporter types.Importer } @@ -176,6 +177,7 @@ func newRulesParser() *rulesParser { itab: typematch.NewImportsTab(stdlib), stdImporter: importer.Default(), srcImporter: importer.ForCompiler(fset, "source", nil), + dslImporter: newDSLImporter(), } } @@ -196,7 +198,7 @@ func (p *rulesParser) ParseFile(filename string, fset *token.FileSet, r io.Reade return nil, fmt.Errorf("expected a gorules package name, found %s", f.Name.Name) } - typechecker := types.Config{Importer: importer.Default()} + typechecker := types.Config{Importer: p.dslImporter} p.types = &types.Info{Types: map[ast.Expr]types.TypeAndValue{}} _, err = typechecker.Check("gorules", fset, []*ast.File{f}, p.types) if err != nil {