diff --git a/attr_cache_benchmark_test.go b/attr_cache_benchmark_test.go index 5f52e50..dfa9608 100644 --- a/attr_cache_benchmark_test.go +++ b/attr_cache_benchmark_test.go @@ -145,10 +145,10 @@ func TestAttributeCacheLRUEviction(t *testing.T) { typ: reflect.TypeOf(types[i]), attr: "name", } - + _, found := attributeCache.m[typeKey] if !found { t.Errorf("Expected type %d to be in cache, but it wasn't", i) } } -} \ No newline at end of file +} diff --git a/filter_benchmark_test.go b/filter_benchmark_test.go index 502ac85..e175f3a 100644 --- a/filter_benchmark_test.go +++ b/filter_benchmark_test.go @@ -11,10 +11,10 @@ import ( func BenchmarkHtmlEscapeFilter(b *testing.B) { ctx := NewRenderContext(nil, nil, nil) defer ctx.Release() - + // Create a string with all the special characters testString := `This is a "test" with & special 'characters' that need escaping` - + b.ResetTimer() for i := 0; i < b.N; i++ { result, _ := ctx.ApplyFilter("escape", testString) @@ -25,7 +25,7 @@ func BenchmarkHtmlEscapeFilter(b *testing.B) { // Benchmark the original nested strings.Replace approach (for comparison) func BenchmarkHtmlEscapeOriginal(b *testing.B) { testString := `This is a "test" with & special 'characters' that need escaping` - + b.ResetTimer() for i := 0; i < b.N; i++ { result := strings.Replace( @@ -46,7 +46,7 @@ func BenchmarkHtmlEscapeOriginal(b *testing.B) { // Benchmark the standard library's html.EscapeString (for comparison) func BenchmarkHtmlEscapeStdLib(b *testing.B) { testString := `This is a "test" with & special 'characters' that need escaping` - + b.ResetTimer() for i := 0; i < b.N; i++ { result := html.EscapeString(testString) @@ -57,10 +57,10 @@ func BenchmarkHtmlEscapeStdLib(b *testing.B) { // Helper for filter chain benchmarks - builds a deep filter chain func createFilterChain(depth int) *FilterNode { var node Node - + // Create a literal base node node = NewLiteralNode("test", 1) - + // Add filters in sequence for i := 0; i < depth; i++ { // Use common filters @@ -75,10 +75,10 @@ func createFilterChain(depth int) *FilterNode { case 3: filterName = "escape" } - + node = NewFilterNode(node, filterName, nil, 1) } - + return node.(*FilterNode) } @@ -100,7 +100,7 @@ func benchmarkFilterChain(b *testing.B, depth int) { env := &Environment{ filters: make(map[string]FilterFunc), } - + // Add filters directly to the map since Environment doesn't expose AddFilter env.filters["upper"] = func(value interface{}, args ...interface{}) (interface{}, error) { return strings.ToUpper(value.(string)), nil @@ -118,13 +118,13 @@ func benchmarkFilterChain(b *testing.B, depth int) { env.filters["escape"] = func(value interface{}, args ...interface{}) (interface{}, error) { return html.EscapeString(value.(string)), nil } - + ctx := NewRenderContext(env, nil, nil) defer ctx.Release() - + // Create a filter chain of the specified depth filterNode := createFilterChain(depth) - + b.ResetTimer() for i := 0; i < b.N; i++ { _, _ = ctx.evaluateFilterNode(filterNode) @@ -140,4 +140,4 @@ func benchmarkToString(value interface{}) string { return s } return fmt.Sprintf("%v", value) -} \ No newline at end of file +} diff --git a/render_filter.go b/render_filter.go index 8d08115..3cca8de 100644 --- a/render_filter.go +++ b/render_filter.go @@ -28,12 +28,12 @@ func (ctx *RenderContext) ApplyFilter(name string, value interface{}, args ...in if str == "" { return "", nil } - + // Preallocate with a reasonable estimate (slightly larger than original) // This avoids most reallocations var b strings.Builder b.Grow(len(str) + len(str)/8) - + // Single-pass iteration is much more efficient than nested Replace calls for _, c := range str { switch c { @@ -51,7 +51,7 @@ func (ctx *RenderContext) ApplyFilter(name string, value interface{}, args ...in b.WriteRune(c) } } - + return b.String(), nil } @@ -81,13 +81,13 @@ func (ctx *RenderContext) DetectFilterChain(node Node) (Node, []FilterChainItem, // Now that we know the depth, allocate the proper size slice chain := make([]FilterChainItem, depth) - + // Traverse the chain again, but this time fill the slice in reverse order // This avoids the O(n²) complexity of the previous implementation currentNode = node for i := depth - 1; i >= 0; i-- { filterNode := currentNode.(*FilterNode) // Safe because we validated in first pass - + // Evaluate filter arguments args := make([]interface{}, len(filterNode.args)) for j, arg := range filterNode.args { @@ -97,17 +97,17 @@ func (ctx *RenderContext) DetectFilterChain(node Node) (Node, []FilterChainItem, } args[j] = val } - + // Add to the chain in the correct position chain[i] = FilterChainItem{ name: filterNode.filter, args: args, } - + // Continue with the next node currentNode = filterNode.node } - + // Return the base node and the chain return currentNode, chain, nil }