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