// SPDX-License-Identifier: AGPL-3.0-only package compiler import ( "fmt" "html" "strings" ) type htmlState uint8 const ( htmlData htmlState = iota htmlAfterLT htmlDeclarationStart htmlDeclarationDash htmlDeclaration htmlComment htmlTagName htmlBeforeAttribute htmlAttributeName htmlAfterAttributeName htmlBeforeAttributeValue htmlAttributeDoubleQuoted htmlAttributeSingleQuoted htmlSelfClosing htmlEndTagName htmlAfterEndTagName htmlRawText htmlRawAfterLT htmlRawEndTagName htmlRawAfterEndTagName ) type contextAnalyzer struct { file *sourceFile state htmlState currentTag string currentAttr string stack []string selfClosing bool commentDash int rawTag string rawEndName string scriptTail string attributes map[string]string seenAttrs map[string]bool attrLiteral strings.Builder attrDynamicNodes []int attrFirstDynamic int attrDynamicPrefix string } var voidElements = map[string]bool{ "area": true, "base": true, "br": true, "col": true, "embed": true, "hr": true, "img": true, "input": true, "link": true, "meta": true, "param": true, "source": true, "track": true, "wbr": true, } var urlAttributes = map[string]bool{ "action": true, "background": true, "cite": true, "classid": true, "code": true, "codebase": true, "data": true, "dynsrc": true, "formaction": true, "href": true, "icon": true, "itemid": true, "longdesc": true, "lowsrc": true, "manifest": true, "poster": true, "profile": true, "src": true, "usemap": true, "xlink:href": true, "xmlns": true, } var unsupportedDynamicAttributes = map[string]string{ "archive": "URL-list", "imagesrcset": "responsive-image URL-list", "itemtype": "URL-list", "ping": "URL-list", "srcdoc": "nested HTML", "srcset": "responsive-image URL-list", } func analyzeContexts(file *sourceFile) []Diagnostic { analyzer := &contextAnalyzer{file: file, state: htmlData, attrFirstDynamic: -1} var diagnostics []Diagnostic for nodeIndex := range file.Nodes { node := &file.Nodes[nodeIndex] switch node.Kind { case nodeText: if d := analyzer.consumeText(node.Text, node.Pos); d != nil { diagnostics = append(diagnostics, *d) return diagnostics } case nodeComment: // Hime-san comments emit no bytes and cannot change HTML state. case nodeStatement: if analyzer.state != htmlData { diagnostics = append(diagnostics, diagnostic(file.Path, node.Pos, "HIM1301", "Go statements are only allowed at HTML content boundaries")) return diagnostics } node.Context = ContextNone case nodeComponent: if analyzer.state != htmlData { diagnostics = append(diagnostics, diagnostic(file.Path, node.Pos, "HIM1302", "component rendering (", analyzer.stack[len(analyzer.stack)-1]))) } return diagnostics } func (a *contextAnalyzer) consumeText(text string, start sourcePosition) *Diagnostic { positionTable := newPositionTable(a.file.Source) for index := 0; index < len(text); index++ { b := text[index] position := positionTable.at(start.Offset + index) if a.rawTag == "script" { a.scriptTail += string(b) if len(a.scriptTail) > len("