Files
zeekay 26a9531f7b merge: upstream/main (sync, brand preserved)
# Conflicts:
#	.github/workflows/check-insights-major-version.yml
#	.github/workflows/check-posthog-major-version.yml
#	.github/workflows/ci-security.yaml
#	.github/workflows/validate-versioning.yml
#	AGENTS.md
#	AGENTS.md~upstream_main
#	packages/ai/src/otel/capture.ts
#	packages/ai/src/otel/mappers/aiSdk.ts
#	packages/ai/src/otel/mappers/index.ts
#	packages/ai/src/otel/types.ts
#	packages/core/src/__tests__/posthog.init.spec.ts
#	packages/react-native/src/PostHogProvider.tsx
#	packages/react/src/context/PostHogContext.ts
#	packages/react/src/context/PostHogProvider.tsx
#	tooling/eslint-plugin-insights/no-unsafe-web-global.js
2026-06-02 10:52:56 -07:00

224 lines
8.0 KiB
JavaScript

const noDirectNullCheck = require('./no-direct-null-check')
const noDirectUndefinedCheck = require('./no-direct-undefined-check')
const noDirectArrayCheck = require('./no-direct-array-check')
const noDirectIsFunctionCheck = require('./no-direct-function-check')
const noDirectObjectCheck = require('./no-direct-object-check')
const noDirectStringCheck = require('./no-direct-string-check')
const noDirectDateCheck = require('./no-direct-date-check')
const noDirectNumberCheck = require('./no-direct-number-check')
const noDirectBooleanCheck = require('./no-direct-boolean-check')
const noAddEventListener = require('./no-add-event-listener')
const noUnsafeWebGlobal = require('./no-unsafe-web-global')
const { RuleTester } = require('eslint')
const ruleTester = new RuleTester({
parserOptions: {
ecmaVersion: 2015,
sourceType: 'module',
},
env: {
browser: true,
},
})
ruleTester.run('no-direct-null-check', noDirectNullCheck, {
valid: [{ code: `isNull(x)` }],
invalid: [{ code: `x === null`, errors: [{ message: 'Use isNull() instead of direct null checks.' }] }],
})
ruleTester.run('no-direct-undefined-check', noDirectUndefinedCheck, {
valid: [{ code: `isUndefined(x)` }],
invalid: [
{
code: `typeof x === undefined`,
errors: [{ message: 'Use isUndefined() instead of direct undefined checks.' }],
},
],
})
ruleTester.run('no-direct-array-check', noDirectArrayCheck, {
valid: [{ code: `isArray(x)` }],
invalid: [
{
code: `Array.isArray(x)`,
errors: [{ message: 'Use isArray() instead of direct array checks.' }],
},
],
})
ruleTester.run('no-direct-is-function-check', noDirectIsFunctionCheck, {
valid: [{ code: `isFunction(x)` }],
invalid: [
{
code: `/^\\s*\\bfunction\\b/.test(x)`,
errors: [{ message: 'Do not use regex to check for functions. Use isFunction instead.' }],
},
{
code: `x instanceof Function`,
errors: [{ message: "Do not use 'instanceof Function' to check for functions. Use isFunction instead." }],
},
{
code: `typeof x === "function"`,
errors: [
{ message: 'Do not use \'typeof x === "function"\' to check for functions. Use isFunction instead.' },
],
},
],
})
ruleTester.run('no-direct-object-check', noDirectObjectCheck, {
valid: [{ code: `isObject(x)` }],
invalid: [
{
code: `obj === Object(obj)`,
errors: [{ message: "Do not use 'obj === Object(obj)'. Use isObject instead." }],
},
],
})
ruleTester.run('no-direct-string-check', noDirectStringCheck, {
valid: [{ code: `isString(x)` }],
invalid: [
{
code: `toString.call(x) == '[object String]'`,
errors: [{ message: 'Use isString instead of direct string checks.' }],
},
{
code: `x instanceof String`,
errors: [{ message: 'Use isString instead of direct string checks.' }],
},
],
})
ruleTester.run('no-direct-date-check', noDirectDateCheck, {
valid: [{ code: `isDate(x)` }],
invalid: [
{
code: `toString.call(obj) == '[object Date]'`,
errors: [{ message: 'Use isDate instead of direct date checks.' }],
},
{
code: `x instanceof Date`,
errors: [{ message: 'Use isDate instead of direct date checks.' }],
},
],
})
ruleTester.run('no-direct-number-check', noDirectNumberCheck, {
valid: [{ code: `isNumber(x)` }],
invalid: [
{
code: `toString.call(obj) == '[object Number]'`,
errors: [{ message: 'Use isNumber instead of direct number checks.' }],
},
{
code: `typeof x === 'number'`,
errors: [{ message: 'Use isNumber instead of direct number checks.' }],
},
],
})
ruleTester.run('no-direct-boolean-check', noDirectBooleanCheck, {
valid: [{ code: `isBoolean(x)` }],
invalid: [
{
code: `toString.call(obj) == '[object Boolean]'`,
errors: [{ message: 'Use isBoolean instead of direct boolean checks.' }],
},
{
code: `typeof x === 'boolean'`,
errors: [{ message: 'Use isBoolean instead of direct boolean checks.' }],
},
],
})
const tsRuleTester = new RuleTester({
parser: require.resolve('@typescript-eslint/parser'),
parserOptions: {
ecmaVersion: 2015,
sourceType: 'module',
},
})
tsRuleTester.run('no-unsafe-web-global', noUnsafeWebGlobal, {
valid: [
// typeof guard is safe
{ code: `typeof Event !== 'undefined'` },
// guarded by typeof via short-circuit &&
{ code: `typeof Event !== 'undefined' && isInstanceOf(candidate, Event)` },
// type annotation
{ code: `function foo(e: Event) {}` },
// type predicate
{ code: `function foo(e: unknown): e is Event { return true }` },
// property access
{ code: `obj.Event` },
// non-web-global identifier
{ code: `const x = SomeOtherThing` },
],
invalid: [
// direct value reference
{
code: `const x = Event`,
errors: [{ messageId: 'unsafeWebGlobal' }],
},
// new expression
{
code: `new Event('test')`,
errors: [{ messageId: 'unsafeWebGlobal' }],
},
// instanceof without typeof guard
{
code: `candidate instanceof Event`,
errors: [{ messageId: 'unsafeWebGlobal' }],
},
// isUndefined(Event) — the original bug pattern
{
code: `!isUndefined(Event) && isInstanceOf(candidate, Event)`,
errors: [
{ messageId: 'unsafeWebGlobal' },
{ messageId: 'unsafeWebGlobal' },
],
},
// other web globals
{
code: `new MutationObserver(() => {})`,
errors: [{ messageId: 'unsafeWebGlobal' }],
},
],
})
ruleTester.run('no-add-event-listener', noAddEventListener, {
valid: [
{ code: "addEventListener(document, 'click', () => {}, { passive: true })" },
{ code: "addEventListener(window, 'scroll', () => {}, { capture: true, passive: true })" },
],
invalid: [
{
code: "document.addEventListener('mouseleave', () => {})",
errors: [{ message: 'Use addEventListener from @utils instead of calling it directly on elements' }],
output: "import { addEventListener } from './utils'\naddEventListener(document, 'mouseleave', () => {})",
},
{
code: "element.addEventListener('click', () => {}, true)",
errors: [{ message: 'Use addEventListener from @utils instead of calling it directly on elements' }],
output: "import { addEventListener } from './utils'\naddEventListener(element, 'click', () => {}, { capture: true })",
},
{
code: "window.addEventListener('click', () => {}, {})",
errors: [{ message: 'Use addEventListener from @utils instead of calling it directly on elements' }],
output: "import { addEventListener } from './utils'\naddEventListener(window, 'click', () => {}, {})",
},
{
code: "document.addEventListener('pageleave', () => {}, { capture: true })",
errors: [{ message: 'Use addEventListener from @utils instead of calling it directly on elements' }],
output: "import { addEventListener } from './utils'\naddEventListener(document, 'pageleave', () => {}, { capture: true })",
},
{
code: "document.addEventListener('pageleave', () => {}, { capture: false })",
errors: [{ message: 'Use addEventListener from @utils instead of calling it directly on elements' }],
output: "import { addEventListener } from './utils'\naddEventListener(document, 'pageleave', () => {}, { capture: false })",
},
],
})