ASTを眺めてるとよく目にするやつ。
ことの発端はすこし別の角度ではあるが、結局こういうのは根本から調べるに限るので・・・。
Literal
ノードのraw
プロパティ
一番わかりやすいのは文字列リテラルに対応するノード。
BabelではStringLiteral
だが、ESTree本家では、数値も真偽値もまとめてLiteral
と名乗ってる。
で、このLiteral
の定義はこうなってる。
interface Literal <: Expression {
type: "Literal";
value: string | boolean | null | number | RegExp;
}
なんと、raw
なんてない。
というわけで、ESTreeで決められてるわけではないらしい。
Esprimaが発祥
って、Issueに書いてあった。
.raw property of literal nodes, or something similar · Issue #14 · estree/estree https://github.com/estree/estree/issues/14
確かにEsprimaでは定義されてた。
interface Literal {
type: 'Literal';
value: boolean | number | string | RegExp | null;
raw: string;
regex?: { pattern: string, flags: string };
}
https://docs.esprima.org/en/latest/syntax-tree-format.html?highlight=raw#literal
というわけで、各parserがそれに追従した結果としての今というわけか。
raw
vs value
そもそもraw
って何やねんvalue
と何が違うんやって。
こういうコードをパースしたら、ASTはだいたいこうなる。
// ['&', "&"]
// ...
[
{
"type": "Literal",
"start": 1,
"end": 4,
"value": "&",
"raw": "'&'"
},
{
"type": "Literal",
"start": 6,
"end": 13,
"value": "&",
"raw": "\"&\""
}
]
// ...
というように、文字列の場合、クオートの有無が異なるポイントになる。
数値の場合は、たとえば0xF
とか書くと、ちゃんと16進数でvalue: 15
となりつつ、raw: "0xF"
になってくれて、ASTをコードに戻すときに便利というやつ。
JSXにもある
現世のASTはもはやESTreeで完結せず、JSXがそのいい例。
で、JSXのASTにあるJSXText
ノードにも、実はraw
プロパティがある。
interface JSXText <: Node {
type: "JSXText";
value: string;
raw: string;
}
あとは、JSXAttribute
ノードのvalue
プロパティにも、Literal
ノードが含まれることがある。
interface JSXAttribute <: Node {
type: "JSXAttribute";
name: JSXIdentifier | JSXNamespacedName;
value: Literal | JSXExpressionContainer | JSXElement | JSXFragment | null;
}
https://github.com/facebook/jsx/blob/main/AST.md#jsx-attributes
というわけで、JSXのASTにおいても、raw
プロパティが見つかる場所はある。
それはそれでいいとして、各parserの実装を追ってみると、(String)Literal
のそれとはまた少し違った実装になってるのを見つけた。
HTMLエンティティの対応
JSX内に登場する文字列関連のraw
プロパティでは、JSX外でのraw
プロパティとは違って、HTMLエンティティが解決される。
つまり、ASTとしてパースされると、このようになる。
// <A p="&">></A>
// ...
{
"type": "JSXAttribute",
"start": 3,
"end": 12,
"name": {
"type": "JSXIdentifier",
"start": 3,
"end": 4,
"name": "p"
},
"value": {
"type": "Literal",
"start": 5,
"end": 12,
"value": "&", // 👈🏻
"raw": "\"&\""
}
}
// ...
{
"type": "JSXText",
"start": 13,
"end": 17,
"value": ">", // 👈🏻
"raw": ">"
}
ちゃんと仕様にも書いてあった。
ただ、どこで対応するかは実装依存らしく、parserはほとんど対応してるけど、TypeScriptはtransform時にやってた。
https://github.com/babel/babel/blob/143064d1b83e56f22db56b2787523a3eb26a5ac6/packages/babel-parser/src/plugins/jsx/index.ts#L239 https://github.com/acornjs/acorn-jsx/blob/8ed96d6ddec2065204ba07d924bb2e7bca539ea6/index.js#L220 https://github.com/microsoft/TypeScript/blob/0aac72020ee8414218273f654eb7ce1dc2dd0d6b/src/compiler/transformers/jsx.ts#L621
う〜ん、AST界隈は奥深いね・・・。