JavaScript 現在具備新的正規表示式強化功能,稱為「比對索引」。想像一下,您想要在 JavaScript 程式碼中找出與保留字重疊的無效變數名稱,並在變數名稱下方輸出一個插入符號和「底線」,例如
const function = foo;
^------- Invalid variable name
在上面的範例中,function
是保留字,不能用作變數名稱。為此,我們可以撰寫以下函式
function displayError(text, message) {
const re = /\b(continue|function|break|for|if)\b/d;
const match = text.match(re);
// Index `1` corresponds to the first capture group.
const [start, end] = match.indices[1];
const error = ' '.repeat(start) + // Adjust the caret position.
'^' +
'-'.repeat(end - start - 1) + // Append the underline.
' ' + message; // Append the message.
console.log(text);
console.log(error);
}
const code = 'const function = foo;'; // faulty code
displayError(code, 'Invalid variable name');
注意:為求簡潔,上面的範例僅包含少部分 JavaScript 保留字。
簡而言之,新的 indices
陣列會儲存每個比對到的擷取群組的開始和結束位置。當原始正規表示式對所有產生正規表示式比對物件的內建函式使用 /d
旗標時,就可以使用這個新陣列,包括 RegExp#exec
、String#match
和 String#matchAll
。
如果您有興趣進一步了解其運作方式,請繼續閱讀。
動機 #
讓我們轉到一個更複雜的範例,並思考您將如何解決剖析程式語言的任務(例如 TypeScript 編譯器 所做的工作)— 首先將輸入原始碼分割成代碼片段,然後為這些代碼片段提供句法結構。如果使用者撰寫了一些句法不正確的程式碼,您會希望向他們顯示有意義的錯誤訊息,理想情況下會指出最先遇到問題程式碼的位置。例如,針對以下程式碼片段
let foo = 42;
// some other code
let foo = 1337;
我們會希望向程式設計師顯示類似這樣的錯誤訊息
let foo = 1337;
^
SyntaxError: Identifier 'foo' has already been declared
為此,我們需要幾個建構區塊,第一個是辨識 TypeScript 識別碼。然後,我們將專注於精確找出錯誤發生位置。讓我們考慮以下範例,使用正規表示式判斷字串是否為有效的識別碼
function isIdentifier(name) {
const re = /^[a-zA-Z_$][0-9a-zA-Z_$]*$/;
return re.exec(name) !== null;
}
注意:實際的剖析器可以使用新推出的 正規表示式中的屬性跳脫字元,並使用以下正規表示式來比對所有有效的 ECMAScript 識別字名稱
const re = /^[$_\p{ID_Start}][$_\u200C\u200D\p{ID_Continue}]*$/u;
為了簡單起見,讓我們堅持使用先前的正規表示式,它只比對拉丁字母、數字和底線。
如果我們遇到類似上述變數宣告的錯誤,並想要列印確切的位置給使用者,我們可能會想要擴充上述正規表示式,並使用類似的函式
function getDeclarationPosition(source) {
const re = /(let|const|var)\s+([a-zA-Z_$][0-9a-zA-Z_$]*)/;
const match = re.exec(source);
if (!match) return -1;
return match.index;
}
可以使用 RegExp.prototype.exec
傳回的比對物件上的 index
屬性,它會傳回整個比對的起始位置。不過,對於像上述所描述的用例,你通常會想要使用(可能是多個)擷取群組。直到最近,JavaScript 尚未公開擷取群組比對到的子字串的開始和結束索引。
正規表示式比對索引說明 #
理想情況下,我們希望在變數名稱的位置列印錯誤,而不是在 let
/const
關鍵字(如上述範例所示)。但為此,我們需要找到索引為 2
的擷取群組的位置。(索引 1
指的是 (let|const|var)
擷取群組,而 0
指的是整個比對。)
如上所述,新的 JavaScript 功能 在 RegExp.prototype.exec()
的結果(子字串陣列)上新增了一個 indices
屬性。讓我們增強上述範例,以利用這個新屬性
function getVariablePosition(source) {
// Notice the `d` flag, which enables `match.indices`
const re = /(let|const|var)\s+([a-zA-Z_$][0-9a-zA-Z_$]*)/d;
const match = re.exec(source);
if (!match) return undefined;
return match.indices[2];
}
getVariablePosition('let foo');
// → [4, 7]
此範例會傳回陣列 [4, 7]
,這是索引為 2
的群組中比對到的子字串的 [start, end)
位置。根據這些資訊,我們的編譯器現在可以列印所需的錯誤。
其他功能 #
indices
物件還包含一個 groups
屬性,可以透過 命名擷取群組 的名稱來索引。使用它,上述函式可以改寫成
function getVariablePosition(source) {
const re = /(?<keyword>let|const|var)\s+(?<id>[a-zA-Z_$][0-9a-zA-Z_$]*)/d;
const match = re.exec(source);
if (!match) return -1;
return match.indices.groups.id;
}
getVariablePosition('let foo');
支援正規表示式比對索引 #
- Chrome: 自版本 90 起支援
- Firefox: 不支援
- Safari: 不支援
- Node.js: 自版本 16 起支援
- Babel: 不支援