Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Add getLoc and getRange to JSONSourceCode #13

Merged
merged 2 commits into from
Jul 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@
"@humanwhocodes/momoa": "^3.1.1"
},
"devDependencies": {
"@eslint/core": "^0.1.0",
"@eslint/core": "^0.3.0",
"@types/eslint": "^8.56.10",
"c8": "^9.1.0",
"eslint": "^9.6.0",
Expand Down
9 changes: 5 additions & 4 deletions src/languages/json-language.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,11 @@ import { visitorKeys } from "@humanwhocodes/momoa";
//-----------------------------------------------------------------------------

/** @typedef {import("@humanwhocodes/momoa").DocumentNode} DocumentNode */
/** @typedef {import("@humanwhocodes/momoa").Node} JSONNode */
/** @typedef {import("@eslint/core").Language} Language */
/** @typedef {import("@eslint/core").OkParseResult<DocumentNode>} OkParseResult */
/** @typedef {import("@eslint/core").ParseResult<DocumentNode>} ParseResult */
/** @typedef {import("@eslint/core").File} File */
/** @typedef {import("@eslint/core").ParseResult} ParseResult */
/** @typedef {import("@eslint/core").SyntaxElement} SyntaxElement */

//-----------------------------------------------------------------------------
// Exports
Expand Down Expand Up @@ -111,7 +112,7 @@ export class JSONLanguage {

return {
ok: true,
ast: /** @type {DocumentNode & SyntaxElement} */ (root),
ast: root,
};
} catch (ex) {
// error messages end with (line:column) so we strip that off for ESLint
Expand All @@ -135,7 +136,7 @@ export class JSONLanguage {
/**
* Creates a new `JSONSourceCode` object from the given information.
* @param {File} file The virtual file to create a `JSONSourceCode` object from.
* @param {ParseResult} parseResult The result returned from `parse()`.
* @param {OkParseResult} parseResult The result returned from `parse()`.
* @returns {JSONSourceCode} The new `JSONSourceCode` object.
*/
createSourceCode(file, parseResult) {
Expand Down
46 changes: 33 additions & 13 deletions src/languages/json-source-code.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,12 @@ import { iterator } from "@humanwhocodes/momoa";
/** @typedef {import("@humanwhocodes/momoa").DocumentNode} DocumentNode */
/** @typedef {import("@humanwhocodes/momoa").Node} JSONNode */
/** @typedef {import("@humanwhocodes/momoa").Token} JSONToken */
/** @typedef {import("@eslint/core").SyntaxElement} SyntaxElement */
/** @typedef {import("@eslint/core").Language} Language */
/** @typedef {import("@eslint/core").SourceRange} SourceRange */
/** @typedef {import("@eslint/core").SourceLocation} SourceLocation */
/** @typedef {import("@eslint/core").File} File */
/** @typedef {import("@eslint/core").TraversalStep} TraversalStep */
/** @typedef {import("@eslint/core").VisitTraversalStep} VisitTraversalStep */
/** @typedef {import("@eslint/core").TextSourceCode} TextSourceCode */
/** @typedef {import("@eslint/core").ParseResult} ParseResult */
/** @typedef {import("@eslint/core").VisitTraversalStep} VisitTraversalStep */

//-----------------------------------------------------------------------------
// Helpers
Expand Down Expand Up @@ -50,7 +49,7 @@ class JSONTraversalStep {

/**
* The target of the step.
* @type {JSONNode & SyntaxElement}
* @type {JSONNode}
*/
target;

Expand All @@ -69,7 +68,7 @@ class JSONTraversalStep {
/**
* Creates a new instance.
* @param {Object} options The options for the step.
* @param {JSONNode & SyntaxElement} options.target The target of the step.
* @param {JSONNode} options.target The target of the step.
* @param {1|2} options.phase The phase of the step.
* @param {Array<any>} options.args The arguments of the step.
*/
Expand Down Expand Up @@ -109,7 +108,7 @@ export class JSONSourceCode {

/**
* The AST of the source code.
* @type {DocumentNode & SyntaxElement}
* @type {DocumentNode}
*/
ast;

Expand All @@ -129,7 +128,7 @@ export class JSONSourceCode {
* Creates a new instance.
* @param {Object} options The options for the instance.
* @param {string} options.text The source code text.
* @param {DocumentNode & SyntaxElement} options.ast The root AST node.
* @param {DocumentNode} options.ast The root AST node.
*/
constructor({ text, ast }) {
this.ast = ast;
Expand All @@ -139,6 +138,28 @@ export class JSONSourceCode {
);
}

/* eslint-disable class-methods-use-this -- Required to complete interface. */

/**
* Returns the loc information for the given node or token.
* @param {JSONNode|JSONToken} nodeOrToken The node or token to get the loc information for.
* @returns {SourceLocation} The loc information for the node or token.
*/
getLoc(nodeOrToken) {
return nodeOrToken.loc;
}

/**
* Returns the range information for the given node or token.
* @param {JSONNode|JSONToken} nodeOrToken The node or token to get the range information for.
* @returns {SourceRange} The range information for the node or token.
*/
getRange(nodeOrToken) {
return nodeOrToken.range;
}

/* eslint-enable class-methods-use-this -- Required to complete interface. */

/**
* Returns the parent of the given node.
* @param {JSONNode} node The node to get the parent of.
Expand Down Expand Up @@ -205,23 +226,22 @@ export class JSONSourceCode {

/**
* Traverse the source code and return the steps that were taken.
* @returns {Iterable<TraversalStep>} The steps that were taken while traversing the source code.
* @returns {Iterable<JSONTraversalStep>} The steps that were taken while traversing the source code.
*/
traverse() {
// Because the AST doesn't mutate, we can cache the steps
if (this.#steps) {
return this.#steps.values();
}

/** @type {Array<JSONTraversalStep>} */
const steps = (this.#steps = []);

for (const { node, parent, phase } of iterator(
/** @type {DocumentNode} */ (this.ast),
)) {
for (const { node, parent, phase } of iterator(this.ast)) {
this.#parents.set(node, parent);
steps.push(
new JSONTraversalStep({
target: /** @type {JSONNode & SyntaxElement} */ (node),
target: node,
phase: phase === "enter" ? 1 : 2,
args: [node, parent],
}),
Expand Down
55 changes: 55 additions & 0 deletions tests/languages/json-source-code.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,61 @@ describe("JSONSourceCode", () => {
});
});

describe("getLoc()", () => {
it("should return the loc property of a node", () => {
const loc = {
start: {
line: 1,
column: 1,
offset: 0,
},
end: {
line: 1,
column: 2,
offset: 1,
},
};
const ast = {
type: "Document",
body: {
type: "Object",
properties: [],
},
tokens: [],
loc,
};
const text = "{}";
const sourceCode = new JSONSourceCode({
text,
ast,
});

assert.strictEqual(sourceCode.getLoc(ast), loc);
});
});

describe("getRange()", () => {
it("should return the range property of a node", () => {
const range = [0, 1];
const ast = {
type: "Document",
body: {
type: "Object",
properties: [],
},
tokens: [],
range,
};
const text = "{}";
const sourceCode = new JSONSourceCode({
text,
ast,
});

assert.strictEqual(sourceCode.getRange(ast), range);
});
});

describe("comments", () => {
it("should contain an empty array when parsing JSON", () => {
const file = { body: "{}", path: "test.json" };
Expand Down