package web
import (
"io/fs"
"path/filepath"
"strings"
"testing"
)
// TestInlineScriptHasNoMultilineStrings 守住「模板里的内联 JS 不许出现跨行字符串」。
//
// JS 的单引号字符串不能含裸换行;真出现一个,那一整块 之间的内容
}
// scriptBlocks 抠出每个 ")
if end < 0 {
break
}
start := line
out = append(out, scriptBlock{line: start, text: rest[:end]})
line += strings.Count(rest[:end], "\n") + strings.Count("", "\n") + 1
s = rest[end:]
}
return out
}
// unterminatedString 找「单/双引号字符串里出现裸换行」的第一处。
// 跳过 // 行注释、/* */ 块注释、反引号模板串,以及 Go 模板动作 {{…}}
// (动作里可能带引号,不该被当成 JS 字符串的边界)。
func unterminatedString(js string) (scriptBlock, bool) {
var (
quote byte // 当前所在字符串的引号,0 表示不在字符串里
tick bool // 反引号模板串
blockC bool // /* */ 里
action int // 处于 {{…}} 里的深度
line = 1
)
for i := 0; i < len(js); i++ {
c := js[i]
if c == '\n' {
line++
if quote != 0 {
from := strings.LastIndex(js[:i], "\n") + 1
return scriptBlock{line: line, text: js[from:i]}, true
}
continue
}
if blockC {
if c == '*' && i+1 < len(js) && js[i+1] == '/' {
blockC = false
i++
}
continue
}
if action > 0 {
if c == '}' && i+1 < len(js) && js[i+1] == '}' {
action = 0
i++
}
continue
}
if tick {
if c == '`' {
tick = false
}
continue
}
if quote != 0 {
if c == '\\' {
i++
} else if c == quote {
quote = 0
}
continue
}
switch {
case c == '{' && i+1 < len(js) && js[i+1] == '{':
action++
i++
case c == '/' && i+1 < len(js) && js[i+1] == '/':
nl := strings.IndexByte(js[i:], '\n')
if nl < 0 {
return scriptBlock{}, false
}
line += strings.Count(js[i:i+nl], "\n")
i += nl
case c == '/' && i+1 < len(js) && js[i+1] == '*':
blockC = true
i++
case c == '`':
tick = true
case c == '\'' || c == '"':
quote = c
}
}
return scriptBlock{}, false
}