()
| 1021 | } |
| 1022 | |
| 1023 | private _consumeAttributeName() { |
| 1024 | const attrNameStart = this._cursor.peek(); |
| 1025 | if (attrNameStart === chars.$SQ || attrNameStart === chars.$DQ) { |
| 1026 | throw this._createError(_unexpectedCharacterErrorMsg(attrNameStart), this._cursor.getSpan()); |
| 1027 | } |
| 1028 | this._beginToken(TokenType.ATTR_NAME); |
| 1029 | let nameEndPredicate: (code: number) => boolean; |
| 1030 | |
| 1031 | if (this._openDirectiveCount > 0) { |
| 1032 | // If we're parsing attributes inside of directive syntax, we have to terminate the name |
| 1033 | // on the first non-matching closing paren. For example, if we have `@Dir(someAttr)`, |
| 1034 | // `@Dir` and `(` will have already been captured as `DIRECTIVE_NAME` and `DIRECTIVE_OPEN` |
| 1035 | // respectively, but the `)` will get captured as a part of the name for `someAttr` |
| 1036 | // because normally that would be an event binding. |
| 1037 | let openParens = 0; |
| 1038 | nameEndPredicate = (code: number) => { |
| 1039 | if (this._openDirectiveCount > 0) { |
| 1040 | if (code === chars.$LPAREN) { |
| 1041 | openParens++; |
| 1042 | } else if (code === chars.$RPAREN) { |
| 1043 | if (openParens === 0) { |
| 1044 | return true; |
| 1045 | } |
| 1046 | openParens--; |
| 1047 | } |
| 1048 | } |
| 1049 | return isNameEnd(code); |
| 1050 | }; |
| 1051 | } else if (attrNameStart === chars.$LBRACKET) { |
| 1052 | let openBrackets = 0; |
| 1053 | |
| 1054 | // Be more permissive for which characters are allowed inside square-bracketed attributes, |
| 1055 | // because they usually end up being bound as attribute values. Some third-party packages |
| 1056 | // like Tailwind take advantage of this. |
| 1057 | nameEndPredicate = (code: number) => { |
| 1058 | if (code === chars.$LBRACKET) { |
| 1059 | openBrackets++; |
| 1060 | } else if (code === chars.$RBRACKET) { |
| 1061 | openBrackets--; |
| 1062 | } |
| 1063 | // Only check for name-ending characters if the brackets are balanced or mismatched. |
| 1064 | // Also interrupt the matching on new lines. |
| 1065 | return openBrackets <= 0 ? isNameEnd(code) : chars.isNewLine(code); |
| 1066 | }; |
| 1067 | } else { |
| 1068 | nameEndPredicate = isNameEnd; |
| 1069 | } |
| 1070 | |
| 1071 | const prefixAndName = this._consumePrefixAndName(nameEndPredicate); |
| 1072 | this._endToken(prefixAndName); |
| 1073 | } |
| 1074 | |
| 1075 | private _consumeAttributeValue() { |
| 1076 | if (this._cursor.peek() === chars.$SQ || this._cursor.peek() === chars.$DQ) { |
no test coverage detected