<commit_before>package template

import (
	"fmt"
	html "html/template"
	"sort"
	text "text/template"
	"text/template/parse"
)

type Validator struct {
	allowRangeNode    bool
	allowTemplateNode bool
	maxDepth          int
}

type ValidatorOption func(*Validator)

func AllowRangeNode(b bool) ValidatorOption {
	return func(v *Validator) {
		v.allowRangeNode = b
	}
}

func AllowTemplateNode(b bool) ValidatorOption {
	return func(v *Validator) {
		v.allowTemplateNode = b
	}
}

func MaxDepth(d int) ValidatorOption {
	return func(v *Validator) {
		v.maxDepth = d
	}
}

func NewValidator(opts ...ValidatorOption) *Validator {
	v := &Validator{}
	for _, opt := range opts {
		opt(v)
	}
	return v
}

func (v *Validator) ValidateTextTemplate(template *text.Template) error {
	tpls := template.Templates()
	sort.Slice(tpls, func(i, j int) bool {
		return tpls[i].Name() < tpls[j].Name()
	})

	for _, tpl := range tpls {
		if err := v.validateTree(tpl.Tree); err != nil {
			return err
		}
	}
	return nil
}

func (v *Validator) ValidateHTMLTemplate(template *html.Template) error {
	tpls := template.Templates()
	sort.Slice(tpls, func(i, j int) bool {
		return tpls[i].Name() < tpls[j].Name()
	})

	for _, tpl := range tpls {
		if err := v.validateTree(tpl.Tree); err != nil {
			return err
		}
	}
	return nil
}

func (v *Validator) validateTree(tree *parse.Tree) (err error) {
	validateFn := func(n parse.Node, depth int) (cont bool) {
		maxDepth := v.maxDepth
		if maxDepth == 0 {
			maxDepth = 4
		}
		if depth > maxDepth {
			err = fmt.Errorf("%s: template nested too deep", formatLocation(tree, n))
		} else {
			switch n := n.(type) {
			case *parse.IfNode, *parse.ListNode, *parse.ActionNode, *parse.TextNode:
				break
			case *parse.PipeNode:
				if len(n.Decl) > 0 {
					err = fmt.Errorf("%s: declaration is forbidden", formatLocation(tree, n))
				} else if len(n.Cmds) > 1 {
					err = fmt.Errorf("%s: pipeline is forbidden", formatLocation(tree, n))
				}
			case *parse.CommandNode:
				for _, arg := range n.Args {
					if ident, ok := arg.(*parse.IdentifierNode); ok {
						if !checkIdentifier(ident.Ident) {
							err = fmt.Errorf("%s: forbidden identifier %s", formatLocation(tree, n), ident.Ident)<commit_msg>Allow declaration in template<commit_after>package template

import (
	"fmt"
	html "html/template"
	"sort"
	text "text/template"
	"text/template/parse"
)

type Validator struct {
	allowRangeNode    bool
	allowTemplateNode bool
	allowDeclaration  bool
	maxDepth          int
}

type ValidatorOption func(*Validator)

func AllowRangeNode(b bool) ValidatorOption {
	return func(v *Validator) {
		v.allowRangeNode = b
	}
}

func AllowTemplateNode(b bool) ValidatorOption {
	return func(v *Validator) {
		v.allowTemplateNode = b
	}
}

func AllowDeclaration(b bool) ValidatorOption {
	return func(v *Validator) {
		v.allowDeclaration = b
	}
}

func MaxDepth(d int) ValidatorOption {
	return func(v *Validator) {
		v.maxDepth = d
	}
}

func NewValidator(opts ...ValidatorOption) *Validator {
	v := &Validator{}
	for _, opt := range opts {
		opt(v)
	}
	return v
}

func (v *Validator) ValidateTextTemplate(template *text.Template) error {
	tpls := template.Templates()
	sort.Slice(tpls, func(i, j int) bool {
		return tpls[i].Name() < tpls[j].Name()
	})

	for _, tpl := range tpls {
		if err := v.validateTree(tpl.Tree); err != nil {
			return err
		}
	}
	return nil
}

func (v *Validator) ValidateHTMLTemplate(template *html.Template) error {
	tpls := template.Templates()
	sort.Slice(tpls, func(i, j int) bool {
		return tpls[i].Name() < tpls[j].Name()
	})

	for _, tpl := range tpls {
		if err := v.validateTree(tpl.Tree); err != nil {
			return err
		}
	}
	return nil
}

func (v *Validator) validateTree(tree *parse.Tree) (err error) {
	validateFn := func(n parse.Node, depth int) (cont bool) {
		maxDepth := v.maxDepth
		if maxDepth == 0 {
			maxDepth = 4
		}
		if depth > maxDepth {
			err = fmt.Errorf("%s: template nested too deep", formatLocation(tree, n))
		} else {
			switch n := n.(type) {
			case *parse.IfNode, *parse.ListNode, *parse.ActionNode, *parse.TextNode:
				break
			case *parse.PipeNode:
				if len(n.Decl) > 0 && !v.allowDeclaration {
					err = fmt.Errorf("%s: declaration is forbidden", formatLocation(tree, n))
				} else if len(n.Cmds) > 1 {
					err = fmt.Errorf("%s: pipeline is forbidden", formatLocation(tree, n))
				}
			case *parse.CommandNode:
				for _, arg := range n.Args {
					if ident, ok := arg.(*parse.IdentifierNode); ok {
						if !checkIdentifier(ident.Ident) {
							err = fmt.Errorf("%s: forbidden identifier %s", formatLocation(tree, n), ident.Ident)
{'fraction_non_alphanumeric': 0.13958017745076823, 'fraction_numerical': 0.0017312270071413113, 'mean_word_length': 3.562685093780849, 'pattern_counts': {'":': 0, '<': 7, '<?xml version=': 0, '>': 9, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1984222', 'n_tokens_mistral': 1769, 'n_tokens_neox': 1628, 'n_words': 426}