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