Ajout de promotion et de commande

This commit is contained in:
Aubert Marvin
2026-04-25 15:28:39 +02:00
parent eddb103755
commit faa3d7718c
8428 changed files with 1126442 additions and 6 deletions
+336
View File
@@ -0,0 +1,336 @@
/**
* @fileoverview JavaScript Language Object
* @author Nicholas C. Zakas
*/
"use strict";
//-----------------------------------------------------------------------------
// Requirements
//-----------------------------------------------------------------------------
const { SourceCode } = require("./source-code");
const createDebug = require("debug");
const astUtils = require("../../shared/ast-utils");
const espree = require("espree");
const eslintScope = require("eslint-scope");
const evk = require("eslint-visitor-keys");
const { validateLanguageOptions } = require("./validate-language-options");
const { LATEST_ECMA_VERSION } = require("../../../conf/ecma-version");
//-----------------------------------------------------------------------------
// Type Definitions
//-----------------------------------------------------------------------------
/** @typedef {import("@eslint/core").File} File */
/** @typedef {import("@eslint/core").Language} Language */
/** @typedef {import("@eslint/core").OkParseResult} OkParseResult */
/** @typedef {import("../../types").Linter.LanguageOptions} JSLanguageOptions */
//-----------------------------------------------------------------------------
// Helpers
//-----------------------------------------------------------------------------
const debug = createDebug("eslint:languages:js");
const DEFAULT_ECMA_VERSION = 5;
const parserSymbol = Symbol.for("eslint.RuleTester.parser");
/**
* Analyze scope of the given AST.
* @param {ASTNode} ast The `Program` node to analyze.
* @param {JSLanguageOptions} languageOptions The parser options.
* @param {Record<string, string[]>} visitorKeys The visitor keys.
* @returns {ScopeManager} The analysis result.
*/
function analyzeScope(ast, languageOptions, visitorKeys) {
const parserOptions = languageOptions.parserOptions;
const ecmaFeatures = parserOptions.ecmaFeatures || {};
const ecmaVersion = languageOptions.ecmaVersion || DEFAULT_ECMA_VERSION;
return eslintScope.analyze(ast, {
ignoreEval: true,
nodejsScope: ecmaFeatures.globalReturn,
impliedStrict: ecmaFeatures.impliedStrict,
ecmaVersion: typeof ecmaVersion === "number" ? ecmaVersion : 6,
sourceType: languageOptions.sourceType || "script",
childVisitorKeys: visitorKeys || evk.KEYS,
fallback: evk.getKeys,
});
}
/**
* Determines if a given object is Espree.
* @param {Object} parser The parser to check.
* @returns {boolean} True if the parser is Espree or false if not.
*/
function isEspree(parser) {
return !!(parser === espree || parser[parserSymbol] === espree);
}
/**
* Normalize ECMAScript version from the initial config into languageOptions (year)
* format.
* @param {any} [ecmaVersion] ECMAScript version from the initial config
* @returns {number} normalized ECMAScript version
*/
function normalizeEcmaVersionForLanguageOptions(ecmaVersion) {
switch (ecmaVersion) {
case 3:
return 3;
// void 0 = no ecmaVersion specified so use the default
case 5:
case void 0:
return 5;
default:
if (typeof ecmaVersion === "number") {
return ecmaVersion >= 2015 ? ecmaVersion : ecmaVersion + 2009;
}
}
/*
* We default to the latest supported ecmaVersion for everything else.
* Remember, this is for languageOptions.ecmaVersion, which sets the version
* that is used for a number of processes inside of ESLint. It's normally
* safe to assume people want the latest unless otherwise specified.
*/
return LATEST_ECMA_VERSION;
}
//-----------------------------------------------------------------------------
// Exports
//-----------------------------------------------------------------------------
/**
* @type {Language}
*/
module.exports = {
fileType: "text",
lineStart: 1,
columnStart: 0,
nodeTypeKey: "type",
visitorKeys: evk.KEYS,
defaultLanguageOptions: {
sourceType: "module",
ecmaVersion: "latest",
parser: espree,
parserOptions: {},
},
validateLanguageOptions,
/**
* Normalizes the language options.
* @param {Object} languageOptions The language options to normalize.
* @returns {Object} The normalized language options.
*/
normalizeLanguageOptions(languageOptions) {
languageOptions.ecmaVersion = normalizeEcmaVersionForLanguageOptions(
languageOptions.ecmaVersion,
);
// Espree expects this information to be passed in
if (isEspree(languageOptions.parser)) {
const parserOptions = languageOptions.parserOptions;
if (languageOptions.sourceType) {
parserOptions.sourceType = languageOptions.sourceType;
if (
parserOptions.sourceType === "module" &&
parserOptions.ecmaFeatures &&
parserOptions.ecmaFeatures.globalReturn
) {
parserOptions.ecmaFeatures.globalReturn = false;
}
}
}
return languageOptions;
},
/**
* Determines if a given node matches a given selector class.
* @param {string} className The class name to check.
* @param {ASTNode} node The node to check.
* @param {Array<ASTNode>} ancestry The ancestry of the node.
* @returns {boolean} True if there's a match, false if not.
* @throws {Error} When an unknown class name is passed.
*/
matchesSelectorClass(className, node, ancestry) {
/*
* Copyright (c) 2013, Joel Feenstra
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of the ESQuery nor the names of its contributors may
* be used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL JOEL FEENSTRA BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
switch (className.toLowerCase()) {
case "statement":
if (node.type.slice(-9) === "Statement") {
return true;
}
// fallthrough: interface Declaration <: Statement { }
case "declaration":
return node.type.slice(-11) === "Declaration";
case "pattern":
if (node.type.slice(-7) === "Pattern") {
return true;
}
// fallthrough: interface Expression <: Node, Pattern { }
case "expression":
return (
node.type.slice(-10) === "Expression" ||
node.type.slice(-7) === "Literal" ||
(node.type === "Identifier" &&
(ancestry.length === 0 ||
ancestry[0].type !== "MetaProperty")) ||
node.type === "MetaProperty"
);
case "function":
return (
node.type === "FunctionDeclaration" ||
node.type === "FunctionExpression" ||
node.type === "ArrowFunctionExpression"
);
default:
throw new Error(`Unknown class name: ${className}`);
}
},
/**
* Parses the given file into an AST.
* @param {File} file The virtual file to parse.
* @param {Object} options Additional options passed from ESLint.
* @param {JSLanguageOptions} options.languageOptions The language options.
* @returns {Object} The result of parsing.
*/
parse(file, { languageOptions }) {
// Note: BOM already removed
const { body: text, path: filePath } = file;
const textToParse = text.replace(
astUtils.shebangPattern,
(match, captured) => `//${captured}`,
);
const { ecmaVersion, sourceType, parser } = languageOptions;
const parserOptions = Object.assign(
{ ecmaVersion, sourceType },
languageOptions.parserOptions,
{
loc: true,
range: true,
raw: true,
tokens: true,
comment: true,
eslintVisitorKeys: true,
eslintScopeManager: true,
filePath,
},
);
/*
* Check for parsing errors first. If there's a parsing error, nothing
* else can happen. However, a parsing error does not throw an error
* from this method - it's just considered a fatal error message, a
* problem that ESLint identified just like any other.
*/
try {
debug("Parsing:", filePath);
const parseResult =
typeof parser.parseForESLint === "function"
? parser.parseForESLint(textToParse, parserOptions)
: { ast: parser.parse(textToParse, parserOptions) };
debug("Parsing successful:", filePath);
const {
ast,
services: parserServices = {},
visitorKeys = evk.KEYS,
scopeManager,
} = parseResult;
return {
ok: true,
ast,
parserServices,
visitorKeys,
scopeManager,
};
} catch (ex) {
// If the message includes a leading line number, strip it:
const message = ex.message.replace(/^line \d+:/iu, "").trim();
debug("%s\n%s", message, ex.stack);
return {
ok: false,
errors: [
{
message,
line: ex.lineNumber,
column: ex.column,
},
],
};
}
},
/**
* Creates a new `SourceCode` object from the given information.
* @param {File} file The virtual file to create a `SourceCode` object from.
* @param {OkParseResult} parseResult The result returned from `parse()`.
* @param {Object} options Additional options passed from ESLint.
* @param {JSLanguageOptions} options.languageOptions The language options.
* @returns {SourceCode} The new `SourceCode` object.
*/
createSourceCode(file, parseResult, { languageOptions }) {
const { body: text, path: filePath, bom: hasBOM } = file;
const { ast, parserServices, visitorKeys } = parseResult;
debug("Scope analysis:", filePath);
const scopeManager =
parseResult.scopeManager ||
analyzeScope(ast, languageOptions, visitorKeys);
debug("Scope analysis successful:", filePath);
return new SourceCode({
text,
ast,
hasBOM,
parserServices,
scopeManager,
visitorKeys,
});
},
};
+7
View File
@@ -0,0 +1,7 @@
"use strict";
const SourceCode = require("./source-code");
module.exports = {
SourceCode,
};
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,61 @@
/**
* @fileoverview Define the cursor which iterates tokens and comments in reverse.
* @author Toru Nagashima
*/
"use strict";
//------------------------------------------------------------------------------
// Requirements
//------------------------------------------------------------------------------
const Cursor = require("./cursor");
const utils = require("./utils");
//------------------------------------------------------------------------------
// Exports
//------------------------------------------------------------------------------
/**
* The cursor which iterates tokens and comments in reverse.
*/
module.exports = class BackwardTokenCommentCursor extends Cursor {
/**
* Initializes this cursor.
* @param {Token[]} tokens The array of tokens.
* @param {Comment[]} comments The array of comments.
* @param {Object} indexMap The map from locations to indices in `tokens`.
* @param {number} startLoc The start location of the iteration range.
* @param {number} endLoc The end location of the iteration range.
*/
constructor(tokens, comments, indexMap, startLoc, endLoc) {
super();
this.tokens = tokens;
this.comments = comments;
this.tokenIndex = utils.getLastIndex(tokens, indexMap, endLoc);
this.commentIndex = utils.search(comments, endLoc) - 1;
this.border = startLoc;
}
/** @inheritdoc */
moveNext() {
const token =
this.tokenIndex >= 0 ? this.tokens[this.tokenIndex] : null;
const comment =
this.commentIndex >= 0 ? this.comments[this.commentIndex] : null;
if (token && (!comment || token.range[1] > comment.range[1])) {
this.current = token;
this.tokenIndex -= 1;
} else if (comment) {
this.current = comment;
this.commentIndex -= 1;
} else {
this.current = null;
}
return (
Boolean(this.current) &&
(this.border === -1 || this.current.range[0] >= this.border)
);
}
};
@@ -0,0 +1,57 @@
/**
* @fileoverview Define the cursor which iterates tokens only in reverse.
* @author Toru Nagashima
*/
"use strict";
//------------------------------------------------------------------------------
// Requirements
//------------------------------------------------------------------------------
const Cursor = require("./cursor");
const { getLastIndex, getFirstIndex } = require("./utils");
//------------------------------------------------------------------------------
// Exports
//------------------------------------------------------------------------------
/**
* The cursor which iterates tokens only in reverse.
*/
module.exports = class BackwardTokenCursor extends Cursor {
/**
* Initializes this cursor.
* @param {Token[]} tokens The array of tokens.
* @param {Comment[]} comments The array of comments.
* @param {Object} indexMap The map from locations to indices in `tokens`.
* @param {number} startLoc The start location of the iteration range.
* @param {number} endLoc The end location of the iteration range.
*/
constructor(tokens, comments, indexMap, startLoc, endLoc) {
super();
this.tokens = tokens;
this.index = getLastIndex(tokens, indexMap, endLoc);
this.indexEnd = getFirstIndex(tokens, indexMap, startLoc);
}
/** @inheritdoc */
moveNext() {
if (this.index >= this.indexEnd) {
this.current = this.tokens[this.index];
this.index -= 1;
return true;
}
return false;
}
/*
*
* Shorthand for performance.
*
*/
/** @inheritdoc */
getOneToken() {
return this.index >= this.indexEnd ? this.tokens[this.index] : null;
}
};
+76
View File
@@ -0,0 +1,76 @@
/**
* @fileoverview Define the abstract class about cursors which iterate tokens.
* @author Toru Nagashima
*/
"use strict";
//------------------------------------------------------------------------------
// Exports
//------------------------------------------------------------------------------
/**
* The abstract class about cursors which iterate tokens.
*
* This class has 2 abstract methods.
*
* - `current: Token | Comment | null` ... The current token.
* - `moveNext(): boolean` ... Moves this cursor to the next token. If the next token didn't exist, it returns `false`.
*
* This is similar to ES2015 Iterators.
* However, Iterators were slow (at 2017-01), so I created this class as similar to C# IEnumerable.
*
* There are the following known sub classes.
*
* - ForwardTokenCursor .......... The cursor which iterates tokens only.
* - BackwardTokenCursor ......... The cursor which iterates tokens only in reverse.
* - ForwardTokenCommentCursor ... The cursor which iterates tokens and comments.
* - BackwardTokenCommentCursor .. The cursor which iterates tokens and comments in reverse.
* - DecorativeCursor
* - FilterCursor ............ The cursor which ignores the specified tokens.
* - SkipCursor .............. The cursor which ignores the first few tokens.
* - LimitCursor ............. The cursor which limits the count of tokens.
*
*/
module.exports = class Cursor {
/**
* Initializes this cursor.
*/
constructor() {
this.current = null;
}
/**
* Gets the first token.
* This consumes this cursor.
* @returns {Token|Comment} The first token or null.
*/
getOneToken() {
return this.moveNext() ? this.current : null;
}
/**
* Gets the first tokens.
* This consumes this cursor.
* @returns {(Token|Comment)[]} All tokens.
*/
getAllTokens() {
const tokens = [];
while (this.moveNext()) {
tokens.push(this.current);
}
return tokens;
}
/**
* Moves this cursor to the next token.
* @returns {boolean} `true` if the next token exists.
* @abstract
*/
/* c8 ignore next */
// eslint-disable-next-line class-methods-use-this -- Unused
moveNext() {
throw new Error("Not implemented.");
}
};
+120
View File
@@ -0,0 +1,120 @@
/**
* @fileoverview Define 2 token factories; forward and backward.
* @author Toru Nagashima
*/
"use strict";
//------------------------------------------------------------------------------
// Requirements
//------------------------------------------------------------------------------
const BackwardTokenCommentCursor = require("./backward-token-comment-cursor");
const BackwardTokenCursor = require("./backward-token-cursor");
const FilterCursor = require("./filter-cursor");
const ForwardTokenCommentCursor = require("./forward-token-comment-cursor");
const ForwardTokenCursor = require("./forward-token-cursor");
const LimitCursor = require("./limit-cursor");
const SkipCursor = require("./skip-cursor");
//------------------------------------------------------------------------------
// Helpers
//------------------------------------------------------------------------------
/**
* The cursor factory.
* @private
*/
class CursorFactory {
/**
* Initializes this cursor.
* @param {Function} TokenCursor The class of the cursor which iterates tokens only.
* @param {Function} TokenCommentCursor The class of the cursor which iterates the mix of tokens and comments.
*/
constructor(TokenCursor, TokenCommentCursor) {
this.TokenCursor = TokenCursor;
this.TokenCommentCursor = TokenCommentCursor;
}
/**
* Creates a base cursor instance that can be decorated by createCursor.
* @param {Token[]} tokens The array of tokens.
* @param {Comment[]} comments The array of comments.
* @param {Object} indexMap The map from locations to indices in `tokens`.
* @param {number} startLoc The start location of the iteration range.
* @param {number} endLoc The end location of the iteration range.
* @param {boolean} includeComments The flag to iterate comments as well.
* @returns {Cursor} The created base cursor.
*/
createBaseCursor(
tokens,
comments,
indexMap,
startLoc,
endLoc,
includeComments,
) {
const Cursor = includeComments
? this.TokenCommentCursor
: this.TokenCursor;
return new Cursor(tokens, comments, indexMap, startLoc, endLoc);
}
/**
* Creates a cursor that iterates tokens with normalized options.
* @param {Token[]} tokens The array of tokens.
* @param {Comment[]} comments The array of comments.
* @param {Object} indexMap The map from locations to indices in `tokens`.
* @param {number} startLoc The start location of the iteration range.
* @param {number} endLoc The end location of the iteration range.
* @param {boolean} includeComments The flag to iterate comments as well.
* @param {Function|null} filter The predicate function to choose tokens.
* @param {number} skip The count of tokens the cursor skips.
* @param {number} count The maximum count of tokens the cursor iterates. Zero is no iteration for backward compatibility.
* @returns {Cursor} The created cursor.
*/
createCursor(
tokens,
comments,
indexMap,
startLoc,
endLoc,
includeComments,
filter,
skip,
count,
) {
let cursor = this.createBaseCursor(
tokens,
comments,
indexMap,
startLoc,
endLoc,
includeComments,
);
if (filter) {
cursor = new FilterCursor(cursor, filter);
}
if (skip >= 1) {
cursor = new SkipCursor(cursor, skip);
}
if (count >= 0) {
cursor = new LimitCursor(cursor, count);
}
return cursor;
}
}
//------------------------------------------------------------------------------
// Exports
//------------------------------------------------------------------------------
module.exports = {
forward: new CursorFactory(ForwardTokenCursor, ForwardTokenCommentCursor),
backward: new CursorFactory(
BackwardTokenCursor,
BackwardTokenCommentCursor,
),
};
@@ -0,0 +1,38 @@
/**
* @fileoverview Define the abstract class about cursors which manipulate another cursor.
* @author Toru Nagashima
*/
"use strict";
//------------------------------------------------------------------------------
// Requirements
//------------------------------------------------------------------------------
const Cursor = require("./cursor");
//------------------------------------------------------------------------------
// Exports
//------------------------------------------------------------------------------
/**
* The abstract class about cursors which manipulate another cursor.
*/
module.exports = class DecorativeCursor extends Cursor {
/**
* Initializes this cursor.
* @param {Cursor} cursor The cursor to be decorated.
*/
constructor(cursor) {
super();
this.cursor = cursor;
}
/** @inheritdoc */
moveNext() {
const retv = this.cursor.moveNext();
this.current = this.cursor.current;
return retv;
}
};
@@ -0,0 +1,42 @@
/**
* @fileoverview Define the cursor which ignores specified tokens.
* @author Toru Nagashima
*/
"use strict";
//------------------------------------------------------------------------------
// Requirements
//------------------------------------------------------------------------------
const DecorativeCursor = require("./decorative-cursor");
//------------------------------------------------------------------------------
// Exports
//------------------------------------------------------------------------------
/**
* The decorative cursor which ignores specified tokens.
*/
module.exports = class FilterCursor extends DecorativeCursor {
/**
* Initializes this cursor.
* @param {Cursor} cursor The cursor to be decorated.
* @param {Function} predicate The predicate function to decide tokens this cursor iterates.
*/
constructor(cursor, predicate) {
super(cursor);
this.predicate = predicate;
}
/** @inheritdoc */
moveNext() {
const predicate = this.predicate;
while (super.moveNext()) {
if (predicate(this.current)) {
return true;
}
}
return false;
}
};
@@ -0,0 +1,65 @@
/**
* @fileoverview Define the cursor which iterates tokens and comments.
* @author Toru Nagashima
*/
"use strict";
//------------------------------------------------------------------------------
// Requirements
//------------------------------------------------------------------------------
const Cursor = require("./cursor");
const { getFirstIndex, search } = require("./utils");
//------------------------------------------------------------------------------
// Exports
//------------------------------------------------------------------------------
/**
* The cursor which iterates tokens and comments.
*/
module.exports = class ForwardTokenCommentCursor extends Cursor {
/**
* Initializes this cursor.
* @param {Token[]} tokens The array of tokens.
* @param {Comment[]} comments The array of comments.
* @param {Object} indexMap The map from locations to indices in `tokens`.
* @param {number} startLoc The start location of the iteration range.
* @param {number} endLoc The end location of the iteration range.
*/
constructor(tokens, comments, indexMap, startLoc, endLoc) {
super();
this.tokens = tokens;
this.comments = comments;
this.tokenIndex = getFirstIndex(tokens, indexMap, startLoc);
this.commentIndex = search(comments, startLoc);
this.border = endLoc;
}
/** @inheritdoc */
moveNext() {
const token =
this.tokenIndex < this.tokens.length
? this.tokens[this.tokenIndex]
: null;
const comment =
this.commentIndex < this.comments.length
? this.comments[this.commentIndex]
: null;
if (token && (!comment || token.range[0] < comment.range[0])) {
this.current = token;
this.tokenIndex += 1;
} else if (comment) {
this.current = comment;
this.commentIndex += 1;
} else {
this.current = null;
}
return (
Boolean(this.current) &&
(this.border === -1 || this.current.range[1] <= this.border)
);
}
};
@@ -0,0 +1,62 @@
/**
* @fileoverview Define the cursor which iterates tokens only.
* @author Toru Nagashima
*/
"use strict";
//------------------------------------------------------------------------------
// Requirements
//------------------------------------------------------------------------------
const Cursor = require("./cursor");
const { getFirstIndex, getLastIndex } = require("./utils");
//------------------------------------------------------------------------------
// Exports
//------------------------------------------------------------------------------
/**
* The cursor which iterates tokens only.
*/
module.exports = class ForwardTokenCursor extends Cursor {
/**
* Initializes this cursor.
* @param {Token[]} tokens The array of tokens.
* @param {Comment[]} comments The array of comments.
* @param {Object} indexMap The map from locations to indices in `tokens`.
* @param {number} startLoc The start location of the iteration range.
* @param {number} endLoc The end location of the iteration range.
*/
constructor(tokens, comments, indexMap, startLoc, endLoc) {
super();
this.tokens = tokens;
this.index = getFirstIndex(tokens, indexMap, startLoc);
this.indexEnd = getLastIndex(tokens, indexMap, endLoc);
}
/** @inheritdoc */
moveNext() {
if (this.index <= this.indexEnd) {
this.current = this.tokens[this.index];
this.index += 1;
return true;
}
return false;
}
/*
*
* Shorthand for performance.
*
*/
/** @inheritdoc */
getOneToken() {
return this.index <= this.indexEnd ? this.tokens[this.index] : null;
}
/** @inheritdoc */
getAllTokens() {
return this.tokens.slice(this.index, this.indexEnd + 1);
}
};
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,39 @@
/**
* @fileoverview Define the cursor which limits the number of tokens.
* @author Toru Nagashima
*/
"use strict";
//------------------------------------------------------------------------------
// Requirements
//------------------------------------------------------------------------------
const DecorativeCursor = require("./decorative-cursor");
//------------------------------------------------------------------------------
// Exports
//------------------------------------------------------------------------------
/**
* The decorative cursor which limits the number of tokens.
*/
module.exports = class LimitCursor extends DecorativeCursor {
/**
* Initializes this cursor.
* @param {Cursor} cursor The cursor to be decorated.
* @param {number} count The count of tokens this cursor iterates.
*/
constructor(cursor, count) {
super(cursor);
this.count = count;
}
/** @inheritdoc */
moveNext() {
if (this.count > 0) {
this.count -= 1;
return super.moveNext();
}
return false;
}
};
@@ -0,0 +1,45 @@
/**
* @fileoverview Define the cursor which iterates tokens only, with inflated range.
* @author Toru Nagashima
*/
"use strict";
//------------------------------------------------------------------------------
// Requirements
//------------------------------------------------------------------------------
const ForwardTokenCursor = require("./forward-token-cursor");
//------------------------------------------------------------------------------
// Exports
//------------------------------------------------------------------------------
/**
* The cursor which iterates tokens only, with inflated range.
* This is for the backward compatibility of padding options.
*/
module.exports = class PaddedTokenCursor extends ForwardTokenCursor {
/**
* Initializes this cursor.
* @param {Token[]} tokens The array of tokens.
* @param {Comment[]} comments The array of comments.
* @param {Object} indexMap The map from locations to indices in `tokens`.
* @param {number} startLoc The start location of the iteration range.
* @param {number} endLoc The end location of the iteration range.
* @param {number} beforeCount The number of tokens this cursor iterates before start.
* @param {number} afterCount The number of tokens this cursor iterates after end.
*/
constructor(
tokens,
comments,
indexMap,
startLoc,
endLoc,
beforeCount,
afterCount,
) {
super(tokens, comments, indexMap, startLoc, endLoc);
this.index = Math.max(0, this.index - beforeCount);
this.indexEnd = Math.min(tokens.length - 1, this.indexEnd + afterCount);
}
};
@@ -0,0 +1,41 @@
/**
* @fileoverview Define the cursor which ignores the first few tokens.
* @author Toru Nagashima
*/
"use strict";
//------------------------------------------------------------------------------
// Requirements
//------------------------------------------------------------------------------
const DecorativeCursor = require("./decorative-cursor");
//------------------------------------------------------------------------------
// Exports
//------------------------------------------------------------------------------
/**
* The decorative cursor which ignores the first few tokens.
*/
module.exports = class SkipCursor extends DecorativeCursor {
/**
* Initializes this cursor.
* @param {Cursor} cursor The cursor to be decorated.
* @param {number} count The count of tokens this cursor skips.
*/
constructor(cursor, count) {
super(cursor);
this.count = count;
}
/** @inheritdoc */
moveNext() {
while (this.count > 0) {
this.count -= 1;
if (!super.moveNext()) {
return false;
}
}
return super.moveNext();
}
};
+110
View File
@@ -0,0 +1,110 @@
/**
* @fileoverview Define utility functions for token store.
* @author Toru Nagashima
*/
"use strict";
//------------------------------------------------------------------------------
// Exports
//------------------------------------------------------------------------------
/**
* Finds the index of the first token which is after the given location.
* If it was not found, this returns `tokens.length`.
* @param {(Token|Comment)[]} tokens It searches the token in this list.
* @param {number} location The location to search.
* @returns {number} The found index or `tokens.length`.
*/
exports.search = function search(tokens, location) {
for (
let minIndex = 0, maxIndex = tokens.length - 1;
minIndex <= maxIndex;
) {
/*
* Calculate the index in the middle between minIndex and maxIndex.
* `| 0` is used to round a fractional value down to the nearest integer: this is similar to
* using `Math.trunc()` or `Math.floor()`, but performance tests have shown this method to
* be faster.
*/
const index = ((minIndex + maxIndex) / 2) | 0;
const token = tokens[index];
const tokenStartLocation = token.range[0];
if (location <= tokenStartLocation) {
if (index === minIndex) {
return index;
}
maxIndex = index;
} else {
minIndex = index + 1;
}
}
return tokens.length;
};
/**
* Gets the index of the `startLoc` in `tokens`.
* `startLoc` can be the value of `node.range[1]`, so this checks about `startLoc - 1` as well.
* @param {(Token|Comment)[]} tokens The tokens to find an index.
* @param {Object} indexMap The map from locations to indices.
* @param {number} startLoc The location to get an index.
* @returns {number} The index.
*/
exports.getFirstIndex = function getFirstIndex(tokens, indexMap, startLoc) {
if (startLoc in indexMap) {
return indexMap[startLoc];
}
if (startLoc - 1 in indexMap) {
const index = indexMap[startLoc - 1];
const token = tokens[index];
// If the mapped index is out of bounds, the returned cursor index will point after the end of the tokens array.
if (!token) {
return tokens.length;
}
/*
* For the map of "comment's location -> token's index", it points the next token of a comment.
* In that case, +1 is unnecessary.
*/
if (token.range[0] >= startLoc) {
return index;
}
return index + 1;
}
return 0;
};
/**
* Gets the index of the `endLoc` in `tokens`.
* The information of end locations are recorded at `endLoc - 1` in `indexMap`, so this checks about `endLoc - 1` as well.
* @param {(Token|Comment)[]} tokens The tokens to find an index.
* @param {Object} indexMap The map from locations to indices.
* @param {number} endLoc The location to get an index.
* @returns {number} The index.
*/
exports.getLastIndex = function getLastIndex(tokens, indexMap, endLoc) {
if (endLoc in indexMap) {
return indexMap[endLoc] - 1;
}
if (endLoc - 1 in indexMap) {
const index = indexMap[endLoc - 1];
const token = tokens[index];
// If the mapped index is out of bounds, the returned cursor index will point before the end of the tokens array.
if (!token) {
return tokens.length - 1;
}
/*
* For the map of "comment's location -> token's index", it points the next token of a comment.
* In that case, -1 is necessary.
*/
if (token.range[1] > endLoc) {
return index - 1;
}
return index;
}
return tokens.length - 1;
};
+196
View File
@@ -0,0 +1,196 @@
/**
* @fileoverview The schema to validate language options
* @author Nicholas C. Zakas
*/
"use strict";
//-----------------------------------------------------------------------------
// Data
//-----------------------------------------------------------------------------
const globalVariablesValues = new Set([
true,
"true",
"writable",
"writeable",
false,
"false",
"readonly",
"readable",
null,
"off",
]);
//------------------------------------------------------------------------------
// Helpers
//------------------------------------------------------------------------------
/**
* Check if a value is a non-null object.
* @param {any} value The value to check.
* @returns {boolean} `true` if the value is a non-null object.
*/
function isNonNullObject(value) {
return typeof value === "object" && value !== null;
}
/**
* Check if a value is a non-null non-array object.
* @param {any} value The value to check.
* @returns {boolean} `true` if the value is a non-null non-array object.
*/
function isNonArrayObject(value) {
return isNonNullObject(value) && !Array.isArray(value);
}
/**
* Check if a value is undefined.
* @param {any} value The value to check.
* @returns {boolean} `true` if the value is undefined.
*/
function isUndefined(value) {
return typeof value === "undefined";
}
//-----------------------------------------------------------------------------
// Schemas
//-----------------------------------------------------------------------------
/**
* Validates the ecmaVersion property.
* @param {string|number} ecmaVersion The value to check.
* @returns {void}
* @throws {TypeError} If the value is invalid.
*/
function validateEcmaVersion(ecmaVersion) {
if (isUndefined(ecmaVersion)) {
throw new TypeError(
'Key "ecmaVersion": Expected an "ecmaVersion" property.',
);
}
if (typeof ecmaVersion !== "number" && ecmaVersion !== "latest") {
throw new TypeError(
'Key "ecmaVersion": Expected a number or "latest".',
);
}
}
/**
* Validates the sourceType property.
* @param {string} sourceType The value to check.
* @returns {void}
* @throws {TypeError} If the value is invalid.
*/
function validateSourceType(sourceType) {
if (
typeof sourceType !== "string" ||
!/^(?:script|module|commonjs)$/u.test(sourceType)
) {
throw new TypeError(
'Key "sourceType": Expected "script", "module", or "commonjs".',
);
}
}
/**
* Validates the globals property.
* @param {Object} globals The value to check.
* @returns {void}
* @throws {TypeError} If the value is invalid.
*/
function validateGlobals(globals) {
if (!isNonArrayObject(globals)) {
throw new TypeError('Key "globals": Expected an object.');
}
for (const key of Object.keys(globals)) {
// avoid hairy edge case
if (key === "__proto__") {
continue;
}
if (key !== key.trim()) {
throw new TypeError(
`Key "globals": Global "${key}" has leading or trailing whitespace.`,
);
}
if (!globalVariablesValues.has(globals[key])) {
throw new TypeError(
`Key "globals": Key "${key}": Expected "readonly", "writable", or "off".`,
);
}
}
}
/**
* Validates the parser property.
* @param {Object} parser The value to check.
* @returns {void}
* @throws {TypeError} If the value is invalid.
*/
function validateParser(parser) {
if (
!parser ||
typeof parser !== "object" ||
(typeof parser.parse !== "function" &&
typeof parser.parseForESLint !== "function")
) {
throw new TypeError(
'Key "parser": Expected object with parse() or parseForESLint() method.',
);
}
}
/**
* Validates the language options.
* @param {Object} languageOptions The language options to validate.
* @returns {void}
* @throws {TypeError} If the language options are invalid.
*/
function validateLanguageOptions(languageOptions) {
if (!isNonArrayObject(languageOptions)) {
throw new TypeError("Expected an object.");
}
const {
ecmaVersion,
sourceType,
globals,
parser,
parserOptions,
...otherOptions
} = languageOptions;
if ("ecmaVersion" in languageOptions) {
validateEcmaVersion(ecmaVersion);
}
if ("sourceType" in languageOptions) {
validateSourceType(sourceType);
}
if ("globals" in languageOptions) {
validateGlobals(globals);
}
if ("parser" in languageOptions) {
validateParser(parser);
}
if ("parserOptions" in languageOptions) {
if (!isNonArrayObject(parserOptions)) {
throw new TypeError('Key "parserOptions": Expected an object.');
}
}
const otherOptionKeys = Object.keys(otherOptions);
if (otherOptionKeys.length > 0) {
throw new TypeError(`Unexpected key "${otherOptionKeys[0]}" found.`);
}
}
module.exports = { validateLanguageOptions };