Skip to content

Commit 02b8925

Browse files
feat(no-misused-observables): interface declarations
1 parent 9882bd9 commit 02b8925

File tree

3 files changed

+213
-1
lines changed

3 files changed

+213
-1
lines changed

src/etc/get-type-services.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,10 @@ export function getTypeServices<
4444
|| ts.isMethodSignature(tsNode)
4545
) {
4646
tsTypeNode = tsNode.type;
47+
} else if (
48+
ts.isPropertySignature(tsNode)
49+
) {
50+
// TODO(#66): this doesn't work for functions assigned to class properties, variables, params.
4751
}
4852
return Boolean(
4953
tsTypeNode

src/rules/no-misused-observables.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ export const noMisusedObservablesRule = ruleCreator({
5353
JSXAttribute: checkJSXAttribute,
5454
ClassDeclaration: checkClassLikeOrInterfaceNode,
5555
ClassExpression: checkClassLikeOrInterfaceNode,
56-
// TSInterfaceDeclaration: checkClassLikeOrInterfaceNode,
56+
TSInterfaceDeclaration: checkClassLikeOrInterfaceNode,
5757
// Property: checkProperty,
5858
// ReturnStatement: checkReturnStatement,
5959
// AssignmentExpression: checkAssignment,

tests/rules/no-misused-observables.test.ts

Lines changed: 208 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,9 +84,33 @@ ruleTester({ types: true }).run('no-misused-observables', noMisusedObservablesRu
8484
const Baz = class extends Foo {
8585
foo(): Observable<number> { return of(42); }
8686
}
87+
88+
interface Qux extends Foo {
89+
foo(): Observable<number>;
90+
}
8791
`,
8892
options: [{ checksVoidReturn: false }],
8993
},
94+
stripIndent`
95+
// void return inherited method; not void
96+
import { Observable, of } from "rxjs";
97+
98+
class Foo {
99+
foo(): Observable<number> { return of(42); }
100+
}
101+
102+
class Bar extends Foo {
103+
foo(): Observable<number> { return of(43); }
104+
}
105+
106+
const Baz = class extends Foo {
107+
foo(): Observable<number> { return of(44); }
108+
}
109+
110+
interface Qux extends Foo {
111+
foo(): Observable<45>;
112+
}
113+
`,
90114
stripIndent`
91115
// void return inherited method; unrelated
92116
class Foo {
@@ -375,6 +399,190 @@ ruleTester({ types: true }).run('no-misused-observables', noMisusedObservablesRu
375399
}
376400
`,
377401
),
402+
fromFixture(
403+
stripIndent`
404+
// void return inherited method; interface; extends class
405+
import { Observable } from "rxjs";
406+
407+
class Foo {
408+
foo(): void {}
409+
}
410+
411+
interface Bar extends Foo {
412+
foo(): Observable<number>;
413+
~~~~~~~~~~~~~~~~~~~~~~~~~~ [forbiddenVoidReturnInheritedMethod { "heritageTypeName": "Foo" }]
414+
}
415+
`,
416+
),
417+
fromFixture(
418+
stripIndent`
419+
// void return inherited method; interface; extends abstract
420+
import { Observable } from "rxjs";
421+
422+
abstract class Foo {
423+
abstract foo(): void;
424+
}
425+
426+
interface Bar extends Foo {
427+
foo(): Observable<number>;
428+
~~~~~~~~~~~~~~~~~~~~~~~~~~ [forbiddenVoidReturnInheritedMethod { "heritageTypeName": "Foo" }]
429+
}
430+
`,
431+
),
432+
fromFixture(
433+
stripIndent`
434+
// void return inherited method; interface; extends interface
435+
import { Observable } from "rxjs";
436+
437+
interface Foo {
438+
foo(): void;
439+
}
440+
441+
interface Bar extends Foo {
442+
foo(): Observable<number>;
443+
~~~~~~~~~~~~~~~~~~~~~~~~~~ [forbiddenVoidReturnInheritedMethod { "heritageTypeName": "Foo" }]
444+
}
445+
`,
446+
),
447+
fromFixture(
448+
stripIndent`
449+
// void return inherited method; interface; extends conditional type
450+
import { Observable } from "rxjs";
451+
452+
type Foo<IsRx extends boolean = true> = IsRx extends true
453+
? { foo(): Observable<void> }
454+
: { foo(): void };
455+
456+
interface Bar extends Foo<false> {
457+
foo(): Observable<void>;
458+
~~~~~~~~~~~~~~~~~~~~~~~~ [forbiddenVoidReturnInheritedMethod { "heritageTypeName": "{ foo(): void; }" }]
459+
}
460+
`,
461+
),
462+
fromFixture(
463+
stripIndent`
464+
// void return inherited method; interface; extends multiple
465+
import { Observable } from "rxjs";
466+
467+
interface Foo {
468+
foo(): void;
469+
}
470+
471+
interface Bar {
472+
foo(): void;
473+
}
474+
475+
interface Baz extends Foo, Bar {
476+
foo(): Observable<void>;
477+
~~~~~~~~~~~~~~~~~~~~~~~~ [forbiddenVoidReturnInheritedMethod { "heritageTypeName": "Foo" }]
478+
~~~~~~~~~~~~~~~~~~~~~~~~ [forbiddenVoidReturnInheritedMethod { "heritageTypeName": "Bar" }]
479+
}
480+
`,
481+
),
482+
fromFixture(
483+
stripIndent`
484+
// void return inherited method; interface; extends multiple classes
485+
import { Observable } from "rxjs";
486+
487+
class Foo {
488+
foo(): void {}
489+
}
490+
491+
class Bar {
492+
foo(): void {}
493+
}
494+
495+
interface Baz extends Foo, Bar {
496+
foo(): Observable<void>;
497+
~~~~~~~~~~~~~~~~~~~~~~~~ [forbiddenVoidReturnInheritedMethod { "heritageTypeName": "Foo" }]
498+
~~~~~~~~~~~~~~~~~~~~~~~~ [forbiddenVoidReturnInheritedMethod { "heritageTypeName": "Bar" }]
499+
}
500+
`,
501+
),
502+
fromFixture(
503+
stripIndent`
504+
// void return inherited method; interface; extends typeof class
505+
import { Observable } from "rxjs";
506+
507+
const Foo = class {
508+
foo(): void {}
509+
}
510+
511+
type Bar = typeof Foo;
512+
513+
interface Baz extends Bar {
514+
foo(): Observable<void>;
515+
~~~~~~~~~~~~~~~~~~~~~~~~ [forbiddenVoidReturnInheritedMethod { "heritageTypeName": "typeof Foo" }]
516+
}
517+
`,
518+
),
519+
fromFixture(
520+
stripIndent`
521+
// void return inherited method; interface; extends function, index, constructor
522+
import { Observable } from "rxjs";
523+
524+
interface Foo {
525+
(): void;
526+
(arg: string): void;
527+
new (): void;
528+
[key: string]: () => void;
529+
[key: number]: () => void;
530+
myMethod(): void;
531+
}
532+
533+
interface Bar extends Foo {
534+
(): Observable<void>;
535+
(arg: string): Observable<void>;
536+
new (): Observable<void>;
537+
[key: string]: () => Observable<void>;
538+
[key: number]: () => Observable<void>;
539+
myMethod(): Observable<void>;
540+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ [forbiddenVoidReturnInheritedMethod { "heritageTypeName": "Foo" }]
541+
}
542+
`,
543+
),
544+
fromFixture(
545+
stripIndent`
546+
// void return inherited method; interface; extends multiple function, index, constructor
547+
import { Observable } from "rxjs";
548+
549+
interface Foo {
550+
(): void;
551+
(arg: string): void;
552+
}
553+
554+
interface Bar {
555+
[key: string]: () => void;
556+
[key: number]: () => void;
557+
}
558+
559+
interface Baz {
560+
new (): void;
561+
new (arg: string): void;
562+
}
563+
564+
interface Qux {
565+
doSyncThing(): void;
566+
doOtherSyncThing(): void;
567+
syncMethodProperty: () => void;
568+
}
569+
570+
interface Quux extends Foo, Bar, Baz, Qux {
571+
(): void;
572+
(arg: string): Observable<void>;
573+
new (): void;
574+
new (arg: string): void;
575+
[key: string]: () => Observable<void>;
576+
[key: number]: () => void;
577+
doSyncThing(): Observable<void>;
578+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ [forbiddenVoidReturnInheritedMethod { "heritageTypeName": "Qux" }]
579+
doRxThing(): Observable<void>;
580+
syncMethodProperty: () => Observable<void>;
581+
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ [forbiddenVoidReturnInheritedMethod { "heritageTypeName": "Qux" }]
582+
// TODO(#66): couldReturnType doesn't work for properties.
583+
}
584+
`,
585+
),
378586
// #endregion invalid; void return inherited method
379587
// #region invalid; spread
380588
fromFixture(

0 commit comments

Comments
 (0)