Skip to content

feat(eslint-plugin): add rules for detecting potential issues#3616

Open
tasshi-me wants to merge 1 commit intomainfrom
feat/eslint-plugin-rules
Open

feat(eslint-plugin): add rules for detecting potential issues#3616
tasshi-me wants to merge 1 commit intomainfrom
feat/eslint-plugin-rules

Conversation

@tasshi-me
Copy link
Member

@tasshi-me tasshi-me commented Feb 10, 2026

Why

Add new ESLint rules to @kintone/eslint-plugin to detect usage of internal APIs and CSS classes that should not be used in kintone customization and plugin development.

These rules are transferred from kintone/cli-kintone#1228 .

What

New Rules

  • no-cybozu-data: Prevents access to cybozu.data, an internal and unsupported API that may change without notice
  • no-kintone-internal-selector: Prevents the use of internal kintone UI class names (gaia-argoui-*, *-gaia, ocean-*, kintone-*)

Config Restructuring

  • recommended: Includes the two new rules (works without manifest file)
  • manifestV2: Includes only-allowed-js-api rule (for plugins with manifest_version v2, experimental)

Documentation

  • Added documentation for each rule
  • Added docs/experimental-new-plugin-system.md
  • Updated package description to "kintone customization and plugin development"
  • Added eslint-plugin to the root README package list

How to test

cd packages/eslint-plugin
pnpm test
pnpm lint

Checklist

  • Read CONTRIBUTING.md
  • Updated documentation if it is required.
  • Added tests if it is required.
  • Passed pnpm lint and pnpm test on the root directory.

@tasshi-me tasshi-me self-assigned this Feb 10, 2026
@tasshi-me tasshi-me marked this pull request as ready for review February 10, 2026 08:06
@tasshi-me tasshi-me requested a review from a team as a code owner February 10, 2026 08:06
@tasshi-me tasshi-me requested review from a team, chihiro-adachi, Copilot and shabaraba and removed request for a team February 10, 2026 08:06
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds new ESLint rules to @kintone/eslint-plugin to flag usage of internal/unsupported kintone APIs and internal UI selectors, along with config/documentation updates.

Changes:

  • Added two new rules: no-cybozu-data and no-kintone-internal-selector (with tests + docs)
  • Updated flat configs: recommended (new rules) and manifestV2 (experimental plugin system)
  • Updated package/README documentation and root package list

Reviewed changes

Copilot reviewed 12 out of 12 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
packages/eslint-plugin/src/rules/no-kintone-internal-selector.ts Implements rule to detect internal CSS class usage in selectors and string literals
packages/eslint-plugin/src/rules/no-cybozu-data.ts Implements rule to detect cybozu.data access
packages/eslint-plugin/src/rules/index.ts Exposes the two new rules from the plugin
packages/eslint-plugin/src/index.ts Extends flat configs to include the new rules and adjusts typing
packages/eslint-plugin/tests/rules/no-kintone-internal-selector.test.ts Adds test cases for internal selector detection
packages/eslint-plugin/tests/rules/no-cybozu-data.test.ts Adds test cases for cybozu.data access detection
packages/eslint-plugin/docs/rules/no-kintone-internal-selector.md Documents the selector rule with examples
packages/eslint-plugin/docs/rules/no-cybozu-data.md Documents the cybozu.data rule with examples
packages/eslint-plugin/docs/experimental-new-plugin-system.md Adds guidance for experimental manifest v2 config
packages/eslint-plugin/README.md Updates plugin description and rule list; links experimental doc
packages/eslint-plugin/package.json Updates package description
README.md Adds eslint-plugin to monorepo package list

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +27 to +38
// Matches:
// cybozu.data
// cybozu["data"]
// cybozu?.data
// cybozu?.["data"]
'MemberExpression[object.type="Identifier"][object.name="cybozu"][property.name="data"]':
(node: TSESTree.Node) => {
context.report({
node,
messageId: "forbiddenCybozuDataAccess",
});
},
Copy link

Copilot AI Feb 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The AST selector only matches non-computed cybozu.data member expressions. It will not match cybozu[\"data\"] (where property is a Literal) and typically won’t match optional chaining forms (cybozu?.data, cybozu?.[\"data\"]) because they are wrapped in a ChainExpression. Update the selectors to also cover computed property access and optional chaining so the implementation matches the documented intent.

Copilot uses AI. Check for mistakes.
Comment on lines +6 to +19
valid: [`const foo = 123;`, `const foo = foo.bar;`],
invalid: [
{
code: `const foo = cybozu.data`,
errors: [{ messageId: "forbiddenCybozuDataAccess" }],
},
{
code: `const foo = cybozu.data.abc`,
errors: [{ messageId: "forbiddenCybozuDataAccess" }],
},
{
code: `const foo = cybozu.data['abc']`,
errors: [{ messageId: "forbiddenCybozuDataAccess" }],
},
Copy link

Copilot AI Feb 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The rule docs claim it covers cybozu[\"data\"] and optional chaining (cybozu?.data, cybozu?.[\"data\"]), but the tests don’t include these patterns. Add explicit invalid cases for those forms to prevent regressions and to validate the intended coverage.

Copilot uses AI. Check for mistakes.
Comment on lines +76 to +78
/[A-Za-z0-9_-]*-gaia/,
/ocean-[A-Za-z0-9_-]*/,
/kintone-[A-Za-z0-9_-]*/,
Copy link

Copilot AI Feb 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The *-gaia pattern (/[A-Za-z0-9_-]*-gaia/) matches foo-gaia as a substring inside longer class names like foo-gaia-bar, even though the docs describe the forbidden form as a suffix (*-gaia). Consider tightening the regex to require a boundary after -gaia (e.g., negative lookahead for [A-Za-z0-9_-]) so foo-gaia-bar doesn’t get flagged as an unintended false positive.

Suggested change
/[A-Za-z0-9_-]*-gaia/,
/ocean-[A-Za-z0-9_-]*/,
/kintone-[A-Za-z0-9_-]*/,
/[A-Za-z0-9_-]*-gaia(?![A-Za-z0-9_-])/,
/ocean-[A-Za-z0-9_-]*/,
/kintone-[A-Za-z09_-]*/,

Copilot uses AI. Check for mistakes.
Comment on lines +6 to +14
ruleTester.run("no-kintone-internal-selector", rule, {
valid: [
`element.querySelector('.foo')`,
`element.querySelectorAll('.foo')`,
`querySelector('.foo')`,
`querySelectorAll('.foo')`,
`$('.foo')`,
`$(document).on('click', '.foo', handler)`,
],
Copy link

Copilot AI Feb 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The rule implementation also targets matches and closest, but the tests don’t include coverage for those call sites (valid/invalid). Adding a couple of cases (e.g., element.closest('.gaia-argoui-foo'), element.matches('.ocean-foo')) would better validate the supported API surface.

Copilot uses AI. Check for mistakes.
@@ -0,0 +1,102 @@
// import type { CallExpression, Literal } from "estree";
Copy link

Copilot AI Feb 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There’s a commented-out import at the top that looks like leftover scaffolding, and the inline NOTE is in Japanese while the rest of the rule/docs are in English. Removing the unused commented import and translating the NOTE to English would improve consistency and long-term maintainability.

Copilot uses AI. Check for mistakes.
callExp: TSESTree.CallExpression,
) => reportForbiddenSelectorCall(context, callExp, checked),

// NOTE: jQuery 等を含む「怪しい文字列リテラル」検知
Copy link

Copilot AI Feb 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There’s a commented-out import at the top that looks like leftover scaffolding, and the inline NOTE is in Japanese while the rest of the rule/docs are in English. Removing the unused commented import and translating the NOTE to English would improve consistency and long-term maintainability.

Copilot uses AI. Check for mistakes.
Copy link
Member Author

@tasshi-me tasshi-me left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

実装メモ

Comment on lines +7 to +8
"no-cybozu-data": noCybozuData,
"no-kintone-internal-selector": noKintoneInternalSelector,
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ルールの作り方はこちら
https://eslint.org/docs/latest/extend/custom-rules

ですが、型の支援が効く @typescript-eslint/utils を使って作成してます。
https://typescript-eslint.io/developers/custom-rules/

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ruleを作るに当たって大きく3つのファイルが必要です。

  • ルールの実装
  • ルールのドキュメント(.md)
  • ルールのテスト

@@ -0,0 +1,30 @@
# Disallow access to the kintone internal API (`no-cybozu-data`)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

/**
* Disallow cybozu.data access
*/
export const rule = createRule<Options, MessageIds>({
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

createRuleというヘルパーを使って作成してます。
(ドキュメント周りのリンクなどをルールに追加したりするのを自動でやってくれるヘルパーです。)

*/
export const rule = createRule<Options, MessageIds>({
name: "no-cybozu-data",
meta: {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ルールの詳細はESLintおよびtypescript-eslintのドキュメントを見てください

schema: [],
},
defaultOptions: [],
create: (context) => ({
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

このあたりが実装です。

estreeというASTを使っています。
https://sosukesuzuki.dev/advent/2022/06/

},
messages: {
forbiddenClassname:
"Using internal kintone UI class name `{{className}}` is not allowed.",
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

context.reportの際に任意のデータをメッセージに含めることができて、今回の場合{{className}}がデータです。

context.report({
node: literal,
messageId: "suspiciousClassnameLiteral",
data: { className: match[0] },
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

メッセージに含めるデータはここで指定しています。


const configs = {
recommended: {
files: ["**/*.{js,cjs,mjs,ts,cts,mts,jsx,tsx}"],
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ここはプリセットの設定です。
よくeslintのプラグインをインストールしたら、.recommendedみたいな設定をeslint.config.jsで読み込むと思いますがそれです。
もともとはonly-allowed-apiというmanifest_version: 2のときのためのルールが入っていたんですけど、それをmanifestV2というプリセットに待避して、recommendedには今回作成したDOMの非推奨操作に関するルールを入れています。
プリセットは今後ルールが増えてきたら適切な粒度で増やしてあげる良さそうです。

import { RuleTester } from "@typescript-eslint/rule-tester";
import { rule } from "../../src/rules/no-cybozu-data.js";

const ruleTester = new RuleTester();
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ここで作ったルールのテストをしています。
詳細は https://typescript-eslint.io/packages/rule-tester/ を参照。

| Name | Description |
| ---------------------------------------------------------- | ------------------------------------------------------------------------ |
| [`only-allowed-js-api`](docs/rules/only-allowed-js-api.md) | Only allow the kintone JS APIs listed in permissions.js_api in manifest. |
See [docs/experimental-new-plugin-system.md](docs/experimental-new-plugin-system.md) for rules supporting plugins with `manifest_version` set to `2`.
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

manifest_version: 2向けのルールは、当面実環境で利用できないので、別ページに移動しました。

Copy link
Contributor

@nameless-mc nameless-mc left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

一旦途中まで

// cybozu["data"]
// cybozu?.data
// cybozu?.["data"]
'MemberExpression[object.type="Identifier"][object.name="cybozu"][property.name="data"]':
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

#3616 (comment)
copilot の指摘に加えて、window.cybozu.dataも検出できなそう

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(どこまで検出するかですが追加で、)一度cybozuを別の変数に格納されると無理そう

) => reportForbiddenSelectorCall(context, callExp, checked),

// NOTE: jQuery 等を含む「怪しい文字列リテラル」検知
Literal: (literal: TSESTree.Literal) => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Literalしか見てないので`.gaia-argoui-foo`みたいなテンプレートリテラルの場合検出できなそう

Comment on lines +24 to +29
languageOptions: {
parserOptions: {
projectService: true,
tsconfigRootDir: dirname(fileURLToPath(import.meta.url)),
},
},
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

requiresTypeChecking: falseしかないので不要そう?

Suggested change
languageOptions: {
parserOptions: {
projectService: true,
tsconfigRootDir: dirname(fileURLToPath(import.meta.url)),
},
},

checked: WeakSet<TSESTree.Literal>,
) => {
for (const arg of node.arguments) {
if (arg.type === "Literal" && typeof arg.value === "string") {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment on lines +39 to +41
messageId: "forbiddenClassname",

data: { className: `gaia-argoui-foo` },
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
messageId: "forbiddenClassname",
data: { className: `gaia-argoui-foo` },
messageId: "forbiddenClassname",
data: { className: `gaia-argoui-foo` },

@tasshi-me tasshi-me changed the title feat(eslint-plugin): add rules for unrecommendable operations feat(eslint-plugin): add rules for detecting potential issues Feb 12, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants