Skip to content
Merged
Show file tree
Hide file tree
Changes from 12 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
13 changes: 10 additions & 3 deletions common/build/eslint-plugin-fluid/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,17 +19,24 @@
"format": "npm run prettier:fix",
"prettier": "prettier --check . --cache --ignore-path ../../../.prettierignore",
"prettier:fix": "prettier --write . --cache --ignore-path ../../../.prettierignore",
"test": "mocha \"src/test/**/*.test.*js\""
"test": "npm run test:both",
"test:both": "npm run test:eslint8 && npm run test:eslint9",
"test:eslint8": "cross-env ESLINT_PACKAGE=eslint8 npm run test:mocha",
"test:eslint9": "cross-env ESLINT_PACKAGE=eslint npm run test:mocha",
"test:mocha": "mocha \"src/test/**/*.test.*js\""
},
"dependencies": {
"@microsoft/tsdoc": "^0.15.1",
"@typescript-eslint/parser": "~7.18.0",
"@typescript-eslint/parser": "~8.46.0",
"@typescript-eslint/utils": "~8.46.0",
"ts-morph": "^22.0.0"
},
"devDependencies": {
"@fluid-tools/markdown-magic": "file:../../../tools/markdown-magic",
"@fluidframework/build-common": "^2.0.3",
"eslint": "^8.57.0",
"cross-env": "^7.0.3",
"eslint": "~9.37.0",
"eslint8": "npm:eslint@~8.57.1",
"mocha": "^10.8.2",
"mocha-multi-reporters": "^1.5.1",
"prettier": "~3.6.2",
Expand Down
432 changes: 335 additions & 97 deletions common/build/eslint-plugin-fluid/pnpm-lock.yaml

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@ module.exports = {
schema: [],
},
create(context) {
const parserServices = context.parserServices;
// ESLint 9+ uses context.sourceCode.parserServices, earlier versions use context.parserServices
const parserServices = context.sourceCode?.parserServices ?? context.parserServices;

// Check if we have the necessary TypeScript services
if (!parserServices || !parserServices.program || !parserServices.esTreeNodeToTSNodeMap) {
Expand All @@ -37,6 +38,15 @@ module.exports = {
return {};
}

// Helper to get scope in both ESLint 8 and 9
// In ESLint 9, getScope requires a node argument
const getScope = (node) => {
if (context.sourceCode?.getScope) {
return context.sourceCode.getScope(node);
}
return context.getScope();
};

// Main function to run on every member access (e.g., obj.a or obj["a"])
function checkPropertyAccess(node) {
if (!isIndexSignatureType(parserServices, node)) {
Expand Down Expand Up @@ -93,7 +103,11 @@ module.exports = {
});
}

if (isStrictlyTypedVariable(getVariableType(parentNode.left, context.getScope()))) {
if (
isStrictlyTypedVariable(
getVariableType(parentNode.left, getScope(parentNode.left)),
)
) {
// This defect occurs when an index signature type is assigned to a strictly typed variable after its declaration
return context.report({
node,
Expand Down Expand Up @@ -132,7 +146,7 @@ module.exports = {
}
const functionDeclaration = findFunctionDeclaration(
parentNode.callee.name,
context.getScope(),
getScope(parentNode.callee),
);
if (!functionDeclaration || !functionDeclaration.params) {
return;
Expand Down Expand Up @@ -629,7 +643,10 @@ function findContainingBlock(node) {
function getKeyValue(node, context) {
if (node.type === "Literal") return node.value;
if (node.type === "Identifier") {
let scope = context.getScope();
// ESLint 9 requires node argument for getScope
let scope = context.sourceCode?.getScope
? context.sourceCode.getScope(node)
: context.getScope();
while (scope) {
const variable = scope.variables.find((v) => v.name === node.name);
if (variable) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,23 +5,17 @@

const assert = require("assert");
const path = require("path");
const { ESLint } = require("eslint");
const { createESLintConfig, eslintVersion, ESLint } = require("../eslintConfigHelper.cjs");

describe("Do not allow file path links in JSDoc/TSDoc comments", function () {
describe(`Do not allow file path links in JSDoc/TSDoc comments (eslint ${eslintVersion})`, function () {
async function lintFile(file) {
const eslint = new ESLint({
useEslintrc: false,
overrideConfig: {
rules: {
"no-file-path-links-in-jsdoc": "error",
},
parser: "@typescript-eslint/parser",
parserOptions: {
project: path.join(__dirname, "../example/tsconfig.json"),
},
const eslintOptions = createESLintConfig({
rules: {
"@fluid-internal/fluid/no-file-path-links-in-jsdoc": "error",
},
rulePaths: [path.join(__dirname, "../../rules")],
});

const eslint = new ESLint(eslintOptions);
const fileToLint = path.join(__dirname, "../example/no-file-path-links-in-jsdoc", file);
const results = await eslint.lintFiles([fileToLint]);
assert.equal(results.length, 1, "Expected a single result for linting a single file.");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,28 +5,22 @@

const assert = require("assert");
const path = require("path");
const { ESLint } = require("eslint");
const { createESLintConfig, eslintVersion, ESLint } = require("../eslintConfigHelper.cjs");

describe("Do not allow `-` following JSDoc/TSDoc tags", function () {
describe(`Do not allow \`-\` following JSDoc/TSDoc tags (eslint ${eslintVersion})`, function () {
/**
*
* @param {string} file - Path to the file being linted. Relative to the `example/no-hyphen-after-jsdoc-tag` folder.
* @returns
*/
async function lintFile(file) {
const eslint = new ESLint({
useEslintrc: false,
overrideConfig: {
rules: {
"no-hyphen-after-jsdoc-tag": "error",
},
parser: "@typescript-eslint/parser",
parserOptions: {
project: path.join(__dirname, "../example/tsconfig.json"),
},
const eslintOptions = createESLintConfig({
rules: {
"@fluid-internal/fluid/no-hyphen-after-jsdoc-tag": "error",
},
rulePaths: [path.join(__dirname, "../../rules")],
});

const eslint = new ESLint(eslintOptions);
const fileToLint = path.join(__dirname, "../example/no-hyphen-after-jsdoc-tag", file);
const results = await eslint.lintFiles([fileToLint]);
assert.equal(results.length, 1, "Expected a single result for linting a single file.");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,23 +5,17 @@

const assert = require("assert");
const path = require("path");
const { ESLint } = require("eslint");
const { createESLintConfig, eslintVersion, ESLint } = require("../eslintConfigHelper.cjs");

describe("Do not allow Markdown links in JSDoc/TSDoc comments", function () {
describe(`Do not allow Markdown links in JSDoc/TSDoc comments (eslint ${eslintVersion})`, function () {
async function lintFile(file) {
const eslint = new ESLint({
useEslintrc: false,
overrideConfig: {
rules: {
"no-markdown-links-in-jsdoc": "error",
},
parser: "@typescript-eslint/parser",
parserOptions: {
project: path.join(__dirname, "../example/tsconfig.json"),
},
const eslintOptions = createESLintConfig({
rules: {
"@fluid-internal/fluid/no-markdown-links-in-jsdoc": "error",
},
rulePaths: [path.join(__dirname, "../../rules")],
});

const eslint = new ESLint(eslintOptions);
const fileToLint = path.join(__dirname, "../example/no-markdown-links-in-jsdoc", file);
const results = await eslint.lintFiles([fileToLint]);
assert.equal(results.length, 1, "Expected a single result for linting a single file.");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,29 +5,15 @@

const assert = require("assert");
const path = require("path");
const { ESLint } = require("eslint");

describe("Do not allow release tags on members", function () {
function createESLintInstance() {
const eslintConfig = {
rules: {
"no-member-release-tags": ["error"],
},
parser: "@typescript-eslint/parser",
parserOptions: {
project: path.join(__dirname, "../example/tsconfig.json"),
},
};

return new ESLint({
useEslintrc: false,
overrideConfig: eslintConfig,
rulePaths: [path.join(__dirname, "../../rules")],
});
}
const { createESLintInstance, eslintVersion } = require("../eslintConfigHelper.cjs");

describe(`Do not allow release tags on members (eslint ${eslintVersion})`, function () {
const eslintRules = {
"@fluid-internal/fluid/no-member-release-tags": ["error"],
};

it("Should report errors for including release tags inside the class declaration", async function () {
const eslint = createESLintInstance();
const eslint = createESLintInstance(eslintRules);

const filesToLint = ["mockClassDeclaration.ts"].map((file) =>
path.join(__dirname, "../example/no-member-release-tags", file),
Expand Down Expand Up @@ -72,7 +58,7 @@ describe("Do not allow release tags on members", function () {
});

it("Should report errors for including release tags inside the class expression", async function () {
const eslint = createESLintInstance();
const eslint = createESLintInstance(eslintRules);

const filesToLint = ["mockClassExpression.ts"].map((file) =>
path.join(__dirname, "../example/no-member-release-tags", file),
Expand Down Expand Up @@ -123,7 +109,7 @@ describe("Do not allow release tags on members", function () {
});

it("Should report errors for including release tags inside the abstract class", async function () {
const eslint = createESLintInstance();
const eslint = createESLintInstance(eslintRules);

const filesToLint = ["mockAbstractClass.ts"].map((file) =>
path.join(__dirname, "../example/no-member-release-tags", file),
Expand All @@ -142,7 +128,7 @@ describe("Do not allow release tags on members", function () {
});

it("Should report errors for including release tags inside the interface", async function () {
const eslint = createESLintInstance();
const eslint = createESLintInstance(eslintRules);

const filesToLint = ["mockInterface.ts"].map((file) =>
path.join(__dirname, "../example/no-member-release-tags", file),
Expand Down Expand Up @@ -181,7 +167,7 @@ describe("Do not allow release tags on members", function () {
});

it("Should report errors for including release tags inside the type", async function () {
const eslint = createESLintInstance();
const eslint = createESLintInstance(eslintRules);

const filesToLint = ["mockType.ts"].map((file) =>
path.join(__dirname, "../example/no-member-release-tags", file),
Expand Down Expand Up @@ -217,7 +203,7 @@ describe("Do not allow release tags on members", function () {
});

it("Should NOT report errors for including release tags for function", async function () {
const eslint = createESLintInstance();
const eslint = createESLintInstance(eslintRules);

const filesToLint = ["mockFunction.ts"].map((file) =>
path.join(__dirname, "../example/no-member-release-tags", file),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,32 +5,21 @@

const assert = require("assert");
const path = require("path");
const { ESLint } = require("eslint");
const { createESLintInstance, eslintVersion } = require("../eslintConfigHelper.cjs");

describe("ESLint Rule Tests", function () {
function createESLintInstance(config) {
return new ESLint({
useEslintrc: false,
overrideConfig: config,
rulePaths: [path.join(__dirname, "../../rules")],
});
}
describe(`ESLint Rule Tests (eslint ${eslintVersion})`, function () {

it("Should report an error for restricted tag imports", async function () {
const eslint = createESLintInstance({
rules: {
"no-restricted-tags-imports": [
"@fluid-internal/fluid/no-restricted-tags-imports": [
"error",
{
tags: ["@internal", "@alpha"],
exceptions: { "@alpha": ["./exceptionFile.ts"] },
},
],
},
parser: "@typescript-eslint/parser",
parserOptions: {
project: path.join(__dirname, "../example/tsconfig.json"),
},
});
const filesToLint = ["fileWithImports.ts", "mockModule.ts"].map((file) =>
path.join(__dirname, "../example/no-restricted-tags-imports", file),
Expand All @@ -51,7 +40,7 @@ describe("ESLint Rule Tests", function () {
it("Should not report an error for restricted tag imports for exceptions", async function () {
const eslint = createESLintInstance({
rules: {
"no-restricted-tags-imports": [
"@fluid-internal/fluid/no-restricted-tags-imports": [
"error",
{
tags: ["@internal", "@alpha"],
Expand All @@ -62,7 +51,6 @@ describe("ESLint Rule Tests", function () {
},
],
},
parser: "@typescript-eslint/parser",
parserOptions: {
project: path.join(__dirname, "../example/tsconfig.json"),
},
Expand All @@ -79,7 +67,7 @@ describe("ESLint Rule Tests", function () {
it("Should report an error for tsconfig provided config", async function () {
const eslint = createESLintInstance({
rules: {
"no-restricted-tags-imports": [
"@fluid-internal/fluid/no-restricted-tags-imports": [
"error",
{
tags: ["@internal", "@alpha"],
Expand All @@ -90,7 +78,6 @@ describe("ESLint Rule Tests", function () {
},
],
},
parser: "@typescript-eslint/parser",
parserOptions: {
project: path.join(__dirname, "../example/tsconfig.json"),
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,23 +5,17 @@

const assert = require("assert");
const path = require("path");
const { ESLint } = require("eslint");
const { createESLintConfig, eslintVersion, ESLint } = require("../eslintConfigHelper.cjs");

describe("ESLint Rule Tests", function () {
describe(`ESLint Rule Tests (eslint ${eslintVersion})`, function () {
async function lintFile(file) {
const eslint = new ESLint({
useEslintrc: false,
overrideConfig: {
rules: {
"no-unchecked-record-access": "error",
},
parser: "@typescript-eslint/parser",
parserOptions: {
project: path.join(__dirname, "../example/tsconfig.json"),
},
const eslintOptions = createESLintConfig({
rules: {
"@fluid-internal/fluid/no-unchecked-record-access": "error",
},
rulePaths: [path.join(__dirname, "../../rules")],
});

const eslint = new ESLint(eslintOptions);
const fileToLint = path.join(__dirname, "../example/no-unchecked-record-access", file);
const results = await eslint.lintFiles([fileToLint]);
return results[0];
Expand Down
Loading