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