-
-
Notifications
You must be signed in to change notification settings - Fork 8
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: add transform for v9 migration
- Loading branch information
Showing
3 changed files
with
260 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,159 @@ | ||
/** | ||
* @fileoverview Transform that migrates an ESLint API from v8 to v9 | ||
* Refer to https://github.com/eslint/eslint-transforms/issues/25 for more information | ||
* | ||
* @author Nitin Kumar | ||
*/ | ||
|
||
"use strict"; | ||
|
||
//------------------------------------------------------------------------------ | ||
// Helpers | ||
//------------------------------------------------------------------------------ | ||
|
||
/** | ||
* Formats a message string with ANSI escape codes to display it in yellow with bold styling in the terminal. | ||
* @param {string} message The message to be formatted. | ||
* @returns {string} The formatted message string. | ||
*/ | ||
function formatBoldYellow(message) { | ||
return `\u001b[1m\u001b[33m${message}\u001b[39m\u001b[22m`; | ||
} | ||
|
||
const contextMethodsToPropertyMapping = { | ||
getSourceCode: "sourceCode", | ||
getFilename: "filename", | ||
getPhysicalFilename: "physicalFilename", | ||
getCwd: "cwd" | ||
}; | ||
|
||
const contextToSourceCodeMapping = { | ||
getSource: "getText", | ||
getSourceLines: "getLines", | ||
getAllComments: "getAllComments", | ||
getNodeByRangeIndex: "getNodeByRangeIndex", | ||
getComments: "getComments", | ||
getCommentsBefore: "getCommentsBefore", | ||
getCommentsAfter: "getCommentsAfter", | ||
getCommentsInside: "getCommentsInside", | ||
getJSDocComment: "getJSDocComment", | ||
getFirstToken: "getFirstToken", | ||
getFirstTokens: "getFirstTokens", | ||
getLastToken: "getLastToken", | ||
getLastTokens: "getLastTokens", | ||
getTokenAfter: "getTokenAfter", | ||
getTokenBefore: "getTokenBefore", | ||
getTokenByRangeStart: "getTokenByRangeStart", | ||
getTokens: "getTokens", | ||
getTokensAfter: "getTokensAfter", | ||
getTokensBefore: "getTokensBefore", | ||
getTokensBetween: "getTokensBetween", | ||
parserServices: "parserServices", | ||
getDeclaredVariables: "getDeclaVariables" | ||
}; | ||
|
||
//------------------------------------------------------------------------------ | ||
// Transform Definition | ||
//------------------------------------------------------------------------------ | ||
|
||
/** | ||
* Transforms an ESLint rule from the old format to the new format. | ||
* @param {Object} fileInfo holds information about the currently processed file. | ||
* * @param {Object} api holds the jscodeshift API | ||
* @returns {string} the new source code, after being transformed. | ||
*/ | ||
|
||
module.exports = function(fileInfo, api) { | ||
const j = api.jscodeshift; | ||
const root = j(fileInfo.source); | ||
|
||
// Update context methods | ||
// context.getSourceCode() -> context.sourceCode ?? context.getSourceCode() | ||
root.find(j.CallExpression, { | ||
callee: { | ||
object: { | ||
type: "Identifier", | ||
name: "context" | ||
}, | ||
property: { | ||
type: "Identifier", | ||
name: name => | ||
Object.keys(contextMethodsToPropertyMapping).includes(name) | ||
} | ||
} | ||
}).replaceWith(({ node }) => { | ||
const method = node.callee.property.name; | ||
const args = node.arguments; | ||
|
||
return j.logicalExpression( | ||
"??", | ||
j.memberExpression( | ||
j.identifier("context"), | ||
j.identifier(contextMethodsToPropertyMapping[method]) | ||
), | ||
j.callExpression( | ||
j.memberExpression( | ||
j.identifier("context"), | ||
j.identifier(method) | ||
), | ||
[...args] | ||
) | ||
); | ||
}); | ||
|
||
// Move context methods to SourceCode | ||
// context.getSource() -> context.sourceCode.getText() | ||
root.find(j.MemberExpression, { | ||
object: { | ||
type: "Identifier", | ||
name: "context" | ||
}, | ||
property: { | ||
type: "Identifier", | ||
name: name => | ||
Object.keys(contextToSourceCodeMapping).includes(name) | ||
} | ||
}).replaceWith(({ node }) => { | ||
const method = node.property.name; | ||
|
||
if (method === "getComments") { | ||
// eslint-disable-next-line no-console -- This is an intentional warning message | ||
console.warn( | ||
formatBoldYellow( | ||
`${fileInfo.path}:${node.loc.start.line}:${node.loc.start.column} The "getComments()" method has been removed. Please use "getCommentsBefore()", "getCommentsAfter()", or "getCommentsInside()" instead. https://eslint.org/docs/latest/use/migrate-to-9.0.0#-removed-sourcecodegetcomments` | ||
) | ||
); | ||
return node; | ||
} | ||
|
||
node.property.name = contextToSourceCodeMapping[method]; | ||
node.object.name = "context.sourceCode"; | ||
|
||
return node; | ||
}); | ||
|
||
// Warn for codePath.currentSegments | ||
root.find(j.Property, { | ||
key: { | ||
type: "Identifier", | ||
name: name => | ||
name === "onCodePathStart" || name === "onCodePathEnd" | ||
} | ||
}) | ||
.find(j.MemberExpression, { | ||
property: { | ||
type: "Identifier", | ||
name: "currentSegments" | ||
} | ||
}) | ||
.forEach(({ node }) => { | ||
// eslint-disable-next-line no-console -- This is an intentional warning message | ||
console.warn( | ||
formatBoldYellow( | ||
`${fileInfo.path}:${node.loc.start.line}:${node.loc.start.column} The "CodePath#currentSegments" property has been removed and it can't be migrated automatically.\nPlease read https://eslint.org/blog/2023/09/preparing-custom-rules-eslint-v9/#codepath%23currentsegments for more information.\n` | ||
) | ||
); | ||
}); | ||
|
||
return root.toSource(); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,100 @@ | ||
/** | ||
* @fileoverview Tests for v9-migration transform. | ||
* @author Nitin Kumar | ||
* MIT License | ||
*/ | ||
|
||
"use strict"; | ||
|
||
const { applyTransform } = require("@hypermod/utils"); | ||
const assert = require("assert"); | ||
|
||
const v9MigrationTransform = require("../../../lib/v9-migration/v9-migration"); | ||
|
||
describe("v9 migration transform", () => { | ||
it("should migrate deprecated context methods to new properties", async () => { | ||
const result = await applyTransform( | ||
v9MigrationTransform, | ||
` | ||
const sourceCode = context.getSourceCode(); | ||
const cwd = context.getCwd(); | ||
const filename = context.getFilename(); | ||
const physicalFilename = context.getPhysicalFilename(); | ||
` | ||
); | ||
|
||
assert.strictEqual( | ||
result, | ||
` | ||
const sourceCode = context.sourceCode ?? context.getSourceCode(); | ||
const cwd = context.cwd ?? context.getCwd(); | ||
const filename = context.filename ?? context.getFilename(); | ||
const physicalFilename = context.physicalFilename ?? context.getPhysicalFilename(); | ||
`.trim() | ||
); | ||
}); | ||
|
||
it("should migrate deprecated context methods to SourceCode", async () => { | ||
const result = await applyTransform( | ||
v9MigrationTransform, | ||
` | ||
const sourceCode = context.getSource(); | ||
const sourceLines = context.getSourceLines(); | ||
const allComments = context.getAllComments(); | ||
const nodeByRangeIndex = context.getNodeByRangeIndex(); | ||
const commentsBefore = context.getCommentsBefore(nodeOrToken); | ||
const commentsAfter = context.getCommentsAfter(nodeOrToken); | ||
const commentsInside = context.getCommentsInside(nodeOrToken); | ||
` | ||
); | ||
|
||
assert.strictEqual( | ||
result, | ||
` | ||
const sourceCode = context.sourceCode.getText(); | ||
const sourceLines = context.sourceCode.getLines(); | ||
const allComments = context.sourceCode.getAllComments(); | ||
const nodeByRangeIndex = context.sourceCode.getNodeByRangeIndex(); | ||
const commentsBefore = context.sourceCode.getCommentsBefore(nodeOrToken); | ||
const commentsAfter = context.sourceCode.getCommentsAfter(nodeOrToken); | ||
`.trim() | ||
); | ||
}); | ||
|
||
it.only("should warn about codePath.currentSegments", async () => { | ||
await applyTransform( | ||
v9MigrationTransform, | ||
` | ||
module.exports = { | ||
meta: { | ||
docs: {}, | ||
schema: [] | ||
}, | ||
create(context) { | ||
return { | ||
onCodePathStart(codePath, node) { | ||
const currentSegments = codePath.currentSegments(); | ||
}, | ||
onCodePathEnd(endCodePath, node) { | ||
const currentSegments = endCodePath.currentSegments(); | ||
}, | ||
}; | ||
} | ||
} | ||
` | ||
); | ||
|
||
// assert.strictEqual( | ||
// result, | ||
// ` | ||
// const sourceCode = context.sourceCode.getText(); | ||
// const sourceLines = context.sourceCode.getLines(); | ||
// const allComments = context.sourceCode.getAllComments(); | ||
// const nodeByRangeIndex = context.sourceCode.getNodeByRangeIndex(); | ||
// const commentsBefore = context.sourceCode.getCommentsBefore(nodeOrToken); | ||
// const commentsAfter = context.sourceCode.getCommentsAfter(nodeOrToken); | ||
// `.trim() | ||
// ); | ||
}); | ||
}); |