diff --git a/packages/analysis/src/jsts/rules/S9145/cb.fixture.js b/packages/analysis/src/jsts/rules/S9145/cb.fixture.js
new file mode 100644
index 00000000000..adaad6361f4
--- /dev/null
+++ b/packages/analysis/src/jsts/rules/S9145/cb.fixture.js
@@ -0,0 +1,11 @@
+import { Vue } from 'vue-class-component';
+
+export default class MyComponent extends Vue { // Noncompliant {{Replace this deprecated Vue class-based component pattern with the Composition API.}}
+ count = 0;
+
+ increment() {
+ this.count++;
+ }
+}
+
+class UnrelatedBaseClass extends SomeOtherBase {} // compliant: superclass is unrelated to vue-class-component
diff --git a/packages/analysis/src/jsts/rules/S9145/cb.fixture.ts b/packages/analysis/src/jsts/rules/S9145/cb.fixture.ts
new file mode 100644
index 00000000000..bd5fdeeb6a4
--- /dev/null
+++ b/packages/analysis/src/jsts/rules/S9145/cb.fixture.ts
@@ -0,0 +1,74 @@
+import { Options, Vue } from 'vue-class-component';
+import { Component as VueComponent, Vue as VueBase } from 'vue-class-component';
+import { Component as FacingComponent } from 'vue-facing-decorator';
+import { Vue as PropertyDecoratorVue, Prop, Watch as VueWatch } from 'vue-property-decorator';
+import DefaultComponent from 'vue-class-component';
+import RealVue from 'vue';
+
+@Options({
+ props: {
+ message: String,
+ },
+})
+export default class MyComponent extends Vue { // Noncompliant {{Replace this deprecated Vue class-based component pattern with the Composition API.}}
+// ^^^^^^^^^^^
+ count = 0;
+}
+
+class ExtendsOnly extends Vue { // Noncompliant {{Replace this deprecated Vue class-based component pattern with the Composition API.}}
+ count = 0;
+
+ increment() {
+ this.count++;
+ }
+}
+
+@VueComponent
+class BareDecoratorOnly {} // Noncompliant {{Replace this deprecated Vue class-based component pattern with the Composition API.}}
+
+class AliasedSuperclass extends VueBase { // Noncompliant {{Replace this deprecated Vue class-based component pattern with the Composition API.}}
+ count = 0;
+}
+
+const AnonymousClassExpression = class extends Vue { // Noncompliant {{Replace this deprecated Vue class-based component pattern with the Composition API.}}
+ count = 0;
+};
+
+// vue-class-component v7 (the Vue 2.7-compatible release) only exports `Component`, as a default
+// export; `Vue` itself is imported from the `vue` package directly, not from vue-class-component
+@DefaultComponent
+export class CanonicalV7Component extends RealVue { // Noncompliant {{Replace this deprecated Vue class-based component pattern with the Composition API.}}
+ count = 0;
+}
+
+function Component(_unused: unknown) {
+ return () => {};
+}
+
+@Component
+class LocallyDefinedDecorator {} // compliant: "Component" here is not imported from vue-class-component
+
+@FacingComponent
+class UsesFacingDecorator {} // compliant: vue-facing-decorator is explicitly out of scope
+
+class UnrelatedBaseClass extends SomeOtherBase {} // compliant: superclass is unrelated to vue-class-component
+
+class UsesPropertyDecorator extends PropertyDecoratorVue { // Noncompliant {{Replace this deprecated Vue class-based component pattern with the Composition API.}}
+ // extends a superclass unrelated to vue-class-component, but the @Prop decorator below
+ // is imported from vue-property-decorator, which is also deprecated and archived
+ @Prop() readonly msg!: string;
+}
+
+class AliasedPropertyDecorator { // Noncompliant {{Replace this deprecated Vue class-based component pattern with the Composition API.}}
+ @VueWatch('someProp')
+ onSomePropChanged() {}
+}
+
+function Watch(_unused: string) {
+ return () => {};
+}
+
+class LocallyDefinedPropertyDecorator {
+ @Watch('someProp') // compliant: "Watch" here is not imported from vue-property-decorator
+ onSomePropChanged() {}
+}
diff --git a/packages/analysis/src/jsts/rules/S9145/cb.fixture.vue b/packages/analysis/src/jsts/rules/S9145/cb.fixture.vue
new file mode 100644
index 00000000000..aad648bb7ca
--- /dev/null
+++ b/packages/analysis/src/jsts/rules/S9145/cb.fixture.vue
@@ -0,0 +1,12 @@
+
diff --git a/packages/analysis/src/jsts/rules/S9145/cb.test.ts b/packages/analysis/src/jsts/rules/S9145/cb.test.ts
new file mode 100644
index 00000000000..9203e277216
--- /dev/null
+++ b/packages/analysis/src/jsts/rules/S9145/cb.test.ts
@@ -0,0 +1,25 @@
+/*
+ * SonarQube JavaScript Plugin
+ * Copyright (C) SonarSource Sàrl
+ * mailto:info AT sonarsource DOT com
+ *
+ * You can redistribute and/or modify this program under the terms of
+ * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the Sonar Source-Available License for more details.
+ *
+ * You should have received a copy of the Sonar Source-Available License
+ * along with this program; if not, see https://sonarsource.com/license/ssal/
+ */
+// https://sonarsource.github.io/rspec/#/rspec/S9145/javascript
+import { test } from '../../../../tests/jsts/tools/testers/comment-based/checker.js';
+import { rule } from './index.js';
+import { describe } from 'node:test';
+import * as meta from './generated-meta.js';
+
+describe(`Rule S9145`, () => {
+ test(meta, rule, import.meta.dirname);
+});
diff --git a/packages/analysis/src/jsts/rules/S9145/index.ts b/packages/analysis/src/jsts/rules/S9145/index.ts
new file mode 100644
index 00000000000..6abe8e6e2e6
--- /dev/null
+++ b/packages/analysis/src/jsts/rules/S9145/index.ts
@@ -0,0 +1,17 @@
+/*
+ * SonarQube JavaScript Plugin
+ * Copyright (C) SonarSource Sàrl
+ * mailto:info AT sonarsource DOT com
+ *
+ * You can redistribute and/or modify this program under the terms of
+ * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the Sonar Source-Available License for more details.
+ *
+ * You should have received a copy of the Sonar Source-Available License
+ * along with this program; if not, see https://sonarsource.com/license/ssal/
+ */
+export { rule } from './rule.js';
diff --git a/packages/analysis/src/jsts/rules/S9145/meta.ts b/packages/analysis/src/jsts/rules/S9145/meta.ts
new file mode 100644
index 00000000000..6f6e3c730ff
--- /dev/null
+++ b/packages/analysis/src/jsts/rules/S9145/meta.ts
@@ -0,0 +1,19 @@
+/*
+ * SonarQube JavaScript Plugin
+ * Copyright (C) SonarSource Sàrl
+ * mailto:info AT sonarsource DOT com
+ *
+ * You can redistribute and/or modify this program under the terms of
+ * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the Sonar Source-Available License for more details.
+ *
+ * You should have received a copy of the Sonar Source-Available License
+ * along with this program; if not, see https://sonarsource.com/license/ssal/
+ */
+// https://sonarsource.github.io/rspec/#/rspec/S9145/javascript
+export const implementation = 'original';
+export const eslintId = 'no-vue-class-component';
diff --git a/packages/analysis/src/jsts/rules/S9145/rule.ts b/packages/analysis/src/jsts/rules/S9145/rule.ts
new file mode 100644
index 00000000000..585b569c5ad
--- /dev/null
+++ b/packages/analysis/src/jsts/rules/S9145/rule.ts
@@ -0,0 +1,172 @@
+/*
+ * SonarQube JavaScript Plugin
+ * Copyright (C) SonarSource Sàrl
+ * mailto:info AT sonarsource DOT com
+ *
+ * You can redistribute and/or modify this program under the terms of
+ * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the Sonar Source-Available License for more details.
+ *
+ * You should have received a copy of the Sonar Source-Available License
+ * along with this program; if not, see https://sonarsource.com/license/ssal/
+ */
+// https://sonarsource.github.io/rspec/#/rspec/S9145/javascript
+
+import type { Rule } from 'eslint';
+import type { TSESTree } from '@typescript-eslint/utils';
+import type estree from 'estree';
+import { intersects, validRange } from 'semver';
+import { isFunctionCall, isIdentifier } from '../helpers/ast.js';
+import { getVueVersion } from '../helpers/dependency-manifests/dependencies.js';
+import { generateMeta } from '../helpers/generate-meta.js';
+import { getFullyQualifiedName } from '../helpers/module.js';
+import * as meta from './generated-meta.js';
+
+const messages = {
+ deprecatedClassComponent:
+ 'Replace this deprecated Vue class-based component pattern with the Composition API.',
+};
+
+const VUE_CLASS_COMPONENT_MODULE = 'vue-class-component';
+const VUE_FQN = 'vue-class-component.Vue';
+const DECORATOR_FQNS = new Set([
+ 'vue-class-component.Component',
+ 'vue-class-component.Options',
+ // vue-class-component v7 (the Vue 2.7-compatible release) only exports `Component`, and as a
+ // default export: `import Component from 'vue-class-component'`. getFullyQualifiedName()
+ // resolves a default import to the bare module name, not a `.Component`-suffixed FQN. `Vue`
+ // itself is never re-exported by vue-class-component in any version (v7 imports it from `vue`
+ // directly, v8 exposes it as a named export), so this bare FQN only ever means the decorator.
+ VUE_CLASS_COMPONENT_MODULE,
+]);
+const PROPERTY_DECORATOR_FQNS = new Set(
+ [
+ 'Prop',
+ 'PropSync',
+ 'Model',
+ 'ModelSync',
+ 'Watch',
+ 'Provide',
+ 'Inject',
+ 'ProvideReactive',
+ 'InjectReactive',
+ 'Emit',
+ 'Ref',
+ 'VModel',
+ ].map(name => `vue-property-decorator.${name}`),
+);
+const VUE_WITH_COMPOSITION_API_RANGE = '>=2.7.0';
+
+type ClassNode = TSESTree.ClassDeclaration | TSESTree.ClassExpression;
+
+export const rule: Rule.RuleModule = {
+ meta: generateMeta(meta, { messages }),
+ create(context: Rule.RuleContext) {
+ if (lacksCompositionApi(context)) {
+ return {};
+ }
+
+ function checkClass(node: estree.Node) {
+ const classNode = node as unknown as ClassNode;
+ if (
+ extendsVueClassComponent(context, classNode) ||
+ hasVueClassComponentDecorator(context, classNode) ||
+ hasVuePropertyDecoratorUsage(context, classNode)
+ ) {
+ context.report({
+ node: getReportNode(classNode),
+ messageId: 'deprecatedClassComponent',
+ });
+ }
+ }
+
+ return {
+ ClassDeclaration: checkClass,
+ ClassExpression: checkClass,
+ };
+ },
+};
+
+/**
+ * Whether the class extends the `Vue` base class imported from `vue-class-component`.
+ * Resolved by import origin, not by the local (possibly aliased) identifier name.
+ */
+function extendsVueClassComponent(context: Rule.RuleContext, classNode: ClassNode): boolean {
+ const { superClass } = classNode;
+ return (
+ superClass?.type === 'Identifier' &&
+ getFullyQualifiedName(context, superClass as unknown as estree.Node) === VUE_FQN
+ );
+}
+
+/**
+ * Whether the class carries a `@Component` or `@Options` decorator imported from
+ * `vue-class-component` (named or, for v7's `Component`, default import), whether used bare
+ * (`@Component`) or called (`@Options({...})`). Resolved by import origin so lookalikes from
+ * other libraries (e.g. `vue-facing-decorator`) or locally-defined decorators of the same name
+ * are not flagged.
+ */
+function hasVueClassComponentDecorator(context: Rule.RuleContext, classNode: ClassNode): boolean {
+ return (classNode.decorators ?? []).some(decorator =>
+ isDecoratorFromModule(context, decorator, DECORATOR_FQNS),
+ );
+}
+
+/**
+ * Whether the class or one of its members carries a `vue-property-decorator` decorator
+ * (`@Prop`, `@Watch`, `@Emit`, ...). `vue-property-decorator` is deprecated and archived just
+ * like `vue-class-component` (which it depends on), so its decorators are an equally strong,
+ * import-resolved signal of a class-based Vue component, even without an explicit `extends Vue`
+ * or `@Component`/`@Options` on the class itself.
+ */
+function hasVuePropertyDecoratorUsage(context: Rule.RuleContext, classNode: ClassNode): boolean {
+ const classDecorators = classNode.decorators ?? [];
+ const memberDecorators = classNode.body.body.flatMap(
+ member => (member as { decorators?: TSESTree.Decorator[] }).decorators ?? [],
+ );
+ return [...classDecorators, ...memberDecorators].some(decorator =>
+ isDecoratorFromModule(context, decorator, PROPERTY_DECORATOR_FQNS),
+ );
+}
+
+/**
+ * Whether `decorator`'s expression, bare (`@Foo`) or called (`@Foo({...})`), resolves by import
+ * origin to one of `fqns`.
+ */
+function isDecoratorFromModule(
+ context: Rule.RuleContext,
+ decorator: TSESTree.Decorator,
+ fqns: ReadonlySet,
+): boolean {
+ const expression = decorator.expression as unknown as estree.Node;
+ const target = isFunctionCall(expression) ? expression.callee : expression;
+ return isIdentifier(target) && fqns.has(getFullyQualifiedName(context, target) ?? '');
+}
+
+function getReportNode(classNode: ClassNode): estree.Node {
+ return (classNode.id ?? classNode) as unknown as estree.Node;
+}
+
+/**
+ * Returns true when the project's Vue dependency range cannot possibly resolve to a version
+ * that has the Composition API.
+ *
+ * vue-class-component's (and vue-property-decorator's) class API was the standard, recommended
+ * way to write components before the Composition API existed, so it is not deprecated on
+ * versions that predate it. Vue backported the Composition API and `