Skip to content

Commit 8d8da20

Browse files
Merge branch 'main' into RemoveJsonValidatorExport
2 parents 991e432 + 0e9a1b9 commit 8d8da20

File tree

11 files changed

+631
-481
lines changed

11 files changed

+631
-481
lines changed

common/build/eslint-plugin-fluid/CHANGELOG.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,12 @@
11
# @fluidframework/eslint-plugin-fluid Changelog
22

3+
## [0.3.0](https://github.com/microsoft/FluidFramework/releases/tag/eslint-plugin-fluid_v0.2.0)
4+
5+
New rules added:
6+
7+
- `@fluid-internal/fluid/no-hyphen-after-jsdoc-tag`: Forbids following a JSDoc/TSDoc comment tag with a `-`.
8+
- Such syntax is commonly used by mistake, due to the fact that `TSDoc` requires a hyphen after the parameter name of a `@param` comment. But no tags want a hyphen between the tag name and the body.
9+
310
## [0.2.0](https://github.com/microsoft/FluidFramework/releases/tag/eslint-plugin-fluid_v0.2.0)
411

512
New rules added:

common/build/eslint-plugin-fluid/index.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,12 @@
1212
*/
1313
module.exports = {
1414
rules: {
15+
/**
16+
* Disallow `-` following JSDoc/TSDoc tags.
17+
* Full name: "@fluid-internal/fluid/no-hyphen-after-jsdoc-tag"
18+
*/
19+
"no-hyphen-after-jsdoc-tag": require("./src/rules/no-hyphen-after-jsdoc-tag"),
20+
1521
/**
1622
* Disallow file path links in JSDoc/TSDoc comments.
1723
* Full name: "@fluid-internal/fluid/no-file-path-links-in-jsdoc"

common/build/eslint-plugin-fluid/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@fluid-internal/eslint-plugin-fluid",
3-
"version": "0.2.0",
3+
"version": "0.3.0",
44
"description": "Custom ESLint rules for the Fluid Framework",
55
"homepage": "https://fluidframework.com",
66
"repository": {
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
/*!
2+
* Copyright (c) Microsoft Corporation and contributors. All rights reserved.
3+
* Licensed under the MIT License.
4+
*/
5+
6+
/**
7+
* JSDoc/TSDoc tags do not require a hyphen after them.
8+
*/
9+
module.exports = {
10+
meta: {
11+
type: "problem",
12+
docs: {
13+
description: "Disallow hyphen character immediately following JSDoc tag",
14+
category: "Best Practices",
15+
recommended: false,
16+
},
17+
messages: {
18+
hyphenAfterTag:
19+
"JSDoc/TSDoc block tags should not be followed by a hyphen character ('-').",
20+
},
21+
fixable: "code",
22+
schema: [],
23+
},
24+
25+
create(context) {
26+
return {
27+
Program() {
28+
const sourceCode = context.getSourceCode();
29+
const comments = sourceCode
30+
.getAllComments()
31+
// Filter to only JSDoc/TSDoc style block comments
32+
.filter((comment) => comment.type === "Block" && comment.value.startsWith("*"));
33+
34+
for (const comment of comments) {
35+
// Find any JSDoc/TSDoc tags followed by a hyphen
36+
const matches = comment.value.matchAll(/(@[a-zA-Z0-9]+)\s*?-(.*)/g);
37+
for (const match of matches) {
38+
const [fullMatch, tag, body] = match;
39+
40+
const startIndex = comment.range[0] + match.index;
41+
const endIndex = startIndex + fullMatch.length;
42+
43+
context.report({
44+
loc: {
45+
start: sourceCode.getLocFromIndex(startIndex),
46+
end: sourceCode.getLocFromIndex(endIndex),
47+
},
48+
messageId: "hyphenAfterTag",
49+
fix(fixer) {
50+
return fixer.replaceTextRange(
51+
[startIndex, endIndex],
52+
`${tag} ${body.trimStart()}`,
53+
);
54+
},
55+
});
56+
}
57+
}
58+
},
59+
};
60+
},
61+
};
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
/*!
2+
* Copyright (c) Microsoft Corporation and contributors. All rights reserved.
3+
* Licensed under the MIT License.
4+
*/
5+
6+
const assert = require("assert");
7+
const path = require("path");
8+
const { ESLint } = require("eslint");
9+
10+
describe("Do not allow `-` following JSDoc/TSDoc tags", function () {
11+
/**
12+
*
13+
* @param {string} file - Path to the file being linted. Relative to the `example/no-hyphen-after-jsdoc-tag` folder.
14+
* @returns
15+
*/
16+
async function lintFile(file) {
17+
const eslint = new ESLint({
18+
useEslintrc: false,
19+
overrideConfig: {
20+
rules: {
21+
"no-hyphen-after-jsdoc-tag": "error",
22+
},
23+
parser: "@typescript-eslint/parser",
24+
parserOptions: {
25+
project: path.join(__dirname, "../example/tsconfig.json"),
26+
},
27+
},
28+
rulePaths: [path.join(__dirname, "../../rules")],
29+
});
30+
const fileToLint = path.join(__dirname, "../example/no-hyphen-after-jsdoc-tag", file);
31+
const results = await eslint.lintFiles([fileToLint]);
32+
assert.equal(results.length, 1, "Expected a single result for linting a single file.");
33+
return results[0];
34+
}
35+
36+
const expectedErrorMessage =
37+
"JSDoc/TSDoc block tags should not be followed by a hyphen character ('-').";
38+
39+
it("Should report errors JSDoc/TSDoc tags followed by a hyphen", async function () {
40+
const result = await lintFile("test.ts");
41+
assert.strictEqual(result.errorCount, 3);
42+
43+
// Error 1
44+
assert.strictEqual(result.messages[0].message, expectedErrorMessage);
45+
assert.strictEqual(result.messages[0].line, 8);
46+
assert.strictEqual(result.messages[0].fix?.text, "@remarks Here are some remarks.");
47+
48+
// Error 2
49+
assert.strictEqual(result.messages[1].message, expectedErrorMessage);
50+
assert.strictEqual(result.messages[1].line, 9);
51+
assert.strictEqual(
52+
result.messages[1].fix?.text,
53+
"@deprecated This function is deprecated, use something else.",
54+
);
55+
56+
// Error 3
57+
assert.strictEqual(result.messages[2].message, expectedErrorMessage);
58+
assert.strictEqual(result.messages[2].line, 10);
59+
assert.strictEqual(result.messages[2].fix?.text, "@returns The concatenated string.");
60+
});
61+
});
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
/*!
2+
* Copyright (c) Microsoft Corporation and contributors. All rights reserved.
3+
* Licensed under the MIT License.
4+
*/
5+
6+
/**
7+
* I am a test function with pretty standard docs, but all of my tags have hyphens after them ☹️.
8+
* @remarks - Here are some remarks.
9+
* @deprecated- This function is deprecated, use something else.
10+
* @returns - The concatenated string.
11+
*/
12+
function invalid<T>(param1: string, param2: T): string {
13+
return `${param1} - ${param2}`;
14+
}
15+
16+
/**
17+
* I am a test function with pretty standard docs, and none of my tags have hyphens after them 🙂.
18+
* @remarks Here are some remarks.
19+
* @deprecated This function is deprecated, use something else.
20+
* @returns The concatenated string.
21+
* @param param1 - I am a param comment. Since my hyphen follows the param name, this is valid.
22+
* @typeParam T - I am a type param comment. I am also valid.
23+
*/
24+
function valid<T>(param1: string, param2: T): string {
25+
return `${param1} - ${param2}`;
26+
}

common/build/eslint-plugin-fluid/src/test/example/no-unchecked-record-access/indexedRecordOfStrings.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -143,11 +143,11 @@ if (key in datastore) {
143143
}
144144
datastore[key][key] = {}; // ok: Accessing nested property key of datastore[key] is allowed because it is assigned in the else case
145145

146-
if (indexedRecordOfStrings.a !== void(0)) {
146+
if (indexedRecordOfStrings.a !== void 0) {
147147
indexedRecordOfStrings.a.length; // ok: Within a presence check, 'a' is guaranteed to be defined
148148
}
149149

150-
if (indexedRecordOfStrings.a !== void(1)) {
150+
if (indexedRecordOfStrings.a !== void 1) {
151151
indexedRecordOfStrings.a.length; // ok: Within a presence check, 'a' is guaranteed to be defined
152152
}
153153

packages/dds/matrix/src/test/fuzz.ts

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ import { isFluidHandle, toFluidHandleInternal } from "@fluidframework/runtime-ut
1919
import type { MatrixItem } from "../ops.js";
2020
import { SharedMatrix, type SharedMatrixFactory } from "../runtime.js";
2121

22+
import { hasSharedMatrixOracle } from "./matrixOracle.js";
23+
2224
/**
2325
* Supported cell values used within the fuzz model.
2426
*/
@@ -225,7 +227,17 @@ export const baseSharedMatrixModel: Omit<
225227
factory: SharedMatrix.getFactory(),
226228
generatorFactory: () => takeAsync(50, makeGenerator()),
227229
reducer: (state, operation) => reducer(state, operation),
228-
validateConsistency: async (a, b) => assertMatricesAreEquivalent(a.channel, b.channel),
230+
validateConsistency: async (a, b) => {
231+
if (hasSharedMatrixOracle(a.channel)) {
232+
a.channel.matrixOracle.validate();
233+
}
234+
235+
if (hasSharedMatrixOracle(b.channel)) {
236+
b.channel.matrixOracle.validate();
237+
}
238+
239+
return assertMatricesAreEquivalent(a.channel, b.channel);
240+
},
229241
minimizationTransforms: ["count", "start", "row", "col"].map((p) => (op) => {
230242
if (p in op && typeof op[p] === "number" && op[p] > 0) {
231243
op[p]--;

packages/dds/matrix/src/test/matrix.fuzz.spec.ts

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,19 +3,29 @@
33
* Licensed under the MIT License.
44
*/
55

6-
import * as path from "node:path";
7-
6+
import { TypedEventEmitter } from "@fluid-internal/client-utils";
87
import {
98
createDDSFuzzSuite,
9+
registerOracle,
10+
type DDSFuzzHarnessEvents,
1011
type DDSFuzzModel,
1112
type DDSFuzzSuiteOptions,
1213
} from "@fluid-private/test-dds-utils";
1314
import { FlushMode } from "@fluidframework/runtime-definitions/internal";
1415

1516
import type { SharedMatrixFactory } from "../runtime.js";
1617

17-
import { _dirname } from "./dirname.cjs";
1818
import { baseSharedMatrixModel, type Operation } from "./fuzz.js";
19+
import { SharedMatrixOracle, type IChannelWithOracles } from "./matrixOracle.js";
20+
21+
const oracleEmitter = new TypedEventEmitter<DDSFuzzHarnessEvents>();
22+
23+
oracleEmitter.on("clientCreate", (client) => {
24+
const channel = client.channel as IChannelWithOracles;
25+
const sharedMatrixOracle = new SharedMatrixOracle(channel);
26+
channel.matrixOracle = sharedMatrixOracle;
27+
registerOracle(sharedMatrixOracle);
28+
});
1929

2030
describe("Matrix fuzz tests", function () {
2131
/**
@@ -39,7 +49,7 @@ describe("Matrix fuzz tests", function () {
3949
clientAddProbability: 0.1,
4050
},
4151
reconnectProbability: 0,
42-
saveFailures: { directory: path.join(_dirname, "../../src/test/results") },
52+
emitter: oracleEmitter,
4353
};
4454

4555
const nameModel = (workloadName: string): DDSFuzzModel<SharedMatrixFactory, Operation> => ({

0 commit comments

Comments
 (0)