@onecx/angular-linter-rules 8.0.0-rc.1 → 8.0.0-rc.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. package/README.md +0 -1
  2. package/package.json +6 -2
  3. package/src/index.d.ts +18 -0
  4. package/src/index.js +12 -0
  5. package/src/index.js.map +1 -0
  6. package/src/lib/configs.d.ts +2 -0
  7. package/src/lib/configs.js +16 -0
  8. package/src/lib/configs.js.map +1 -0
  9. package/src/lib/rules/index.d.ts +11 -0
  10. package/src/lib/rules/index.js +12 -0
  11. package/src/lib/rules/index.js.map +1 -0
  12. package/src/lib/rules/no-subscribe-assignment.rule.d.ts +4 -0
  13. package/src/lib/rules/no-subscribe-assignment.rule.js +132 -0
  14. package/src/lib/rules/no-subscribe-assignment.rule.js.map +1 -0
  15. package/src/lib/rules/no-translate-instant.rule.d.ts +4 -0
  16. package/src/lib/rules/no-translate-instant.rule.js +82 -0
  17. package/src/lib/rules/no-translate-instant.rule.js.map +1 -0
  18. package/src/lib/rules/prefer-translate-params.rule.d.ts +4 -0
  19. package/src/lib/rules/prefer-translate-params.rule.js +128 -0
  20. package/src/lib/rules/prefer-translate-params.rule.js.map +1 -0
  21. package/src/lib/types.d.ts +5 -0
  22. package/src/lib/types.js +3 -0
  23. package/src/lib/types.js.map +1 -0
  24. package/src/lib/utils/type-utils.d.ts +2 -0
  25. package/src/lib/utils/type-utils.js +17 -0
  26. package/src/lib/utils/type-utils.js.map +1 -0
  27. package/src/version.d.ts +2 -0
  28. package/src/version.js +6 -0
  29. package/src/version.js.map +1 -0
  30. package/eslint.config.cjs +0 -19
  31. package/jest.config.ts +0 -10
  32. package/project.json +0 -28
  33. package/src/index.ts +0 -11
  34. package/src/lib/configs.ts +0 -16
  35. package/src/lib/rules/index.ts +0 -9
  36. package/src/lib/rules/no-subscribe-assignment.rule.spec.ts +0 -118
  37. package/src/lib/rules/no-subscribe-assignment.rule.ts +0 -149
  38. package/src/lib/rules/no-translate-instant.rule.spec.ts +0 -122
  39. package/src/lib/rules/no-translate-instant.rule.ts +0 -98
  40. package/src/lib/rules/prefer-translate-params.rule.spec.ts +0 -97
  41. package/src/lib/rules/prefer-translate-params.rule.ts +0 -133
  42. package/src/lib/types.ts +0 -6
  43. package/src/lib/utils/type-utils.ts +0 -15
  44. package/src/types/rule-tester.d.ts +0 -3
  45. package/src/version.ts +0 -2
  46. package/tsconfig.json +0 -23
  47. package/tsconfig.lib.json +0 -10
  48. package/tsconfig.rule-tester.json +0 -5
  49. package/tsconfig.spec.json +0 -10
package/src/index.ts DELETED
@@ -1,11 +0,0 @@
1
- import { rules } from './lib/rules'
2
- import { configs } from './lib/configs'
3
-
4
- export { rules, configs }
5
-
6
- export const plugin = {
7
- rules,
8
- configs,
9
- }
10
-
11
- export type { AngularLinterRulesPlugin } from './lib/types'
@@ -1,16 +0,0 @@
1
- import type { TSESLint } from '@typescript-eslint/utils'
2
- import { rules } from './rules'
3
-
4
- const pluginName = '@onecx/angular-linter-rules'
5
-
6
- const recommendedRules = Object.keys(rules).reduce<Record<string, TSESLint.Linter.RuleEntry>>((acc, ruleName) => {
7
- acc[`${pluginName}/${ruleName}`] = 'warn'
8
- return acc
9
- }, {})
10
-
11
- export const configs: Record<string, TSESLint.Linter.Config> = {
12
- recommended: {
13
- plugins: [pluginName],
14
- rules: recommendedRules,
15
- },
16
- }
@@ -1,9 +0,0 @@
1
- import { noSubscribeAssignment } from './no-subscribe-assignment.rule'
2
- import { noTranslateInstant } from './no-translate-instant.rule'
3
- import { preferTranslateParams } from './prefer-translate-params.rule'
4
-
5
- export const rules = {
6
- 'no-subscribe-assignment': noSubscribeAssignment,
7
- 'no-translate-instant': noTranslateInstant,
8
- 'prefer-translate-params': preferTranslateParams,
9
- } as const
@@ -1,118 +0,0 @@
1
- import { RuleTester } from '@typescript-eslint/rule-tester'
2
- import { noSubscribeAssignment } from './no-subscribe-assignment.rule'
3
-
4
- RuleTester.afterAll = afterAll
5
-
6
- const ruleTester = new RuleTester({
7
- languageOptions: {
8
- parser: require('@typescript-eslint/parser'),
9
- parserOptions: {
10
- ecmaVersion: 2022,
11
- sourceType: 'module',
12
- },
13
- },
14
- } as unknown as any)
15
-
16
- const messageId = 'assignmentOutside'
17
-
18
- ruleTester.run('no-subscribe-assignment', noSubscribeAssignment, {
19
- valid: [
20
- {
21
- code: `
22
- import { of } from 'rxjs'
23
-
24
- function test() {
25
- of(1).subscribe((value) => {
26
- const local = value
27
- console.log(local)
28
- })
29
- }
30
- `,
31
- },
32
- {
33
- code: `
34
- import { of } from 'rxjs'
35
-
36
- function test() {
37
- let outer = 0
38
- of(1).subscribe((value) => {
39
- const outer = value
40
- console.log(outer)
41
- })
42
- console.log(outer)
43
- }
44
- `,
45
- },
46
- {
47
- code: `
48
- import { of } from 'rxjs'
49
-
50
- function test() {
51
- of({ a: 1 }).subscribe((value) => {
52
- const { a } = value
53
- console.log(a)
54
- })
55
- }
56
- `,
57
- },
58
- ],
59
- invalid: [
60
- {
61
- code: `
62
- import { of } from 'rxjs'
63
-
64
- function test() {
65
- let outer: number | undefined
66
- of(1).subscribe((value) => {
67
- outer = value
68
- })
69
- console.log(outer)
70
- }
71
- `,
72
- errors: [{ messageId }],
73
- },
74
- {
75
- code: `
76
- import { of } from 'rxjs'
77
-
78
- class A {
79
- value?: number
80
- test() {
81
- of(1).subscribe((value) => {
82
- this.value = value
83
- })
84
- }
85
- }
86
- `,
87
- errors: [{ messageId }],
88
- },
89
- {
90
- code: `
91
- import { of } from 'rxjs'
92
-
93
- function test() {
94
- let outer = 0
95
- of(1).subscribe(function (value) {
96
- outer++
97
- })
98
- console.log(outer)
99
- }
100
- `,
101
- errors: [{ messageId }],
102
- },
103
- {
104
- code: `
105
- import { of } from 'rxjs'
106
-
107
- function test() {
108
- let outer = 0
109
- of(1).subscribe((value) => {
110
- ;({ outer } = { outer: value })
111
- })
112
- console.log(outer)
113
- }
114
- `,
115
- errors: [{ messageId }],
116
- },
117
- ],
118
- })
@@ -1,149 +0,0 @@
1
- import { AST_NODE_TYPES, ESLintUtils, TSESTree } from '@typescript-eslint/utils'
2
-
3
- const createRule = ESLintUtils.RuleCreator((name) => `https://github.com/onecx/onecx-portal-ui-libs/tree/main/libs/angular-linter-rules#${name}`)
4
-
5
- type Options = []
6
- type MessageIds = 'assignmentOutside'
7
-
8
- function isSubscribeCall(node: TSESTree.CallExpression): boolean {
9
- return node.callee.type === AST_NODE_TYPES.MemberExpression &&
10
- node.callee.property.type === AST_NODE_TYPES.Identifier &&
11
- node.callee.property.name === 'subscribe'
12
- }
13
-
14
- function getFunctionArg(node: TSESTree.CallExpression): TSESTree.FunctionExpression | TSESTree.ArrowFunctionExpression | undefined {
15
- for (const arg of node.arguments) {
16
- if (arg.type === AST_NODE_TYPES.ArrowFunctionExpression || arg.type === AST_NODE_TYPES.FunctionExpression) {
17
- return arg
18
- }
19
- }
20
- return undefined
21
- }
22
-
23
- type PatternLike =
24
- | TSESTree.Identifier
25
- | TSESTree.ArrayPattern
26
- | TSESTree.ObjectPattern
27
- | TSESTree.RestElement
28
- | TSESTree.AssignmentPattern
29
-
30
- function getAssignedIdentifiers(pattern: PatternLike): TSESTree.Identifier[] {
31
- switch (pattern.type) {
32
- case AST_NODE_TYPES.Identifier:
33
- return [pattern]
34
- case AST_NODE_TYPES.ArrayPattern:
35
- return pattern.elements.flatMap((el) => (el ? getAssignedIdentifiers(el as PatternLike) : []))
36
- case AST_NODE_TYPES.ObjectPattern:
37
- return pattern.properties.flatMap((p) => {
38
- if (p.type === AST_NODE_TYPES.Property) return getAssignedIdentifiers(p.value as PatternLike)
39
- if (p.type === AST_NODE_TYPES.RestElement) return getAssignedIdentifiers(p.argument as PatternLike)
40
- return []
41
- })
42
- case AST_NODE_TYPES.RestElement:
43
- return getAssignedIdentifiers(pattern.argument as PatternLike)
44
- case AST_NODE_TYPES.AssignmentPattern:
45
- return getAssignedIdentifiers(pattern.left as PatternLike)
46
- }
47
- }
48
-
49
- function getDeclaredVariablesInFunctionBody(
50
- body: TSESTree.BlockStatement | TSESTree.Expression,
51
- ): Set<string> {
52
- const names = new Set<string>()
53
- if (body.type !== AST_NODE_TYPES.BlockStatement) return names
54
-
55
- for (const stmt of body.body) {
56
- if (stmt.type !== AST_NODE_TYPES.VariableDeclaration) continue
57
- for (const decl of stmt.declarations) {
58
- for (const id of getAssignedIdentifiers(decl.id as any)) {
59
- names.add(id.name)
60
- }
61
- }
62
- }
63
-
64
- return names
65
- }
66
-
67
- export const noSubscribeAssignment = createRule<Options, MessageIds>({
68
- name: 'no-subscribe-assignment',
69
- meta: {
70
- type: 'problem',
71
- docs: {
72
- description:
73
- 'Warn when assigning inside an Observable subscribe callback to variables declared outside the callback (including class members).',
74
- },
75
- schema: [],
76
- messages: {
77
- assignmentOutside: 'Avoid assigning to outer-scope variables inside `subscribe`. Prefer `map/tap` + `async` pipe or return the value.',
78
- },
79
- },
80
- defaultOptions: [],
81
- create(context) {
82
- return {
83
- CallExpression(node) {
84
- if (!isSubscribeCall(node)) return
85
-
86
- const callback = getFunctionArg(node)
87
- if (!callback) return
88
-
89
- const callbackBody = callback.body
90
- const localDeclarations = getDeclaredVariablesInFunctionBody(callbackBody)
91
-
92
- const reportIfOuter = (identifier: TSESTree.Identifier) => {
93
- if (localDeclarations.has(identifier.name)) return
94
- context.report({
95
- node: identifier,
96
- messageId: 'assignmentOutside',
97
- })
98
- }
99
-
100
- const sourceCode = context.sourceCode
101
- const visitor = (n: TSESTree.Node) => {
102
- if (n.type === AST_NODE_TYPES.AssignmentExpression) {
103
- if (n.left.type === AST_NODE_TYPES.MemberExpression) {
104
- if (n.left.object.type === AST_NODE_TYPES.ThisExpression) {
105
- context.report({ node: n.left, messageId: 'assignmentOutside' })
106
- }
107
- return
108
- }
109
-
110
- if (n.left.type === AST_NODE_TYPES.Identifier) {
111
- reportIfOuter(n.left)
112
- return
113
- }
114
-
115
- if (n.left.type === AST_NODE_TYPES.ObjectPattern || n.left.type === AST_NODE_TYPES.ArrayPattern) {
116
- for (const id of getAssignedIdentifiers(n.left)) {
117
- reportIfOuter(id)
118
- }
119
- }
120
- return
121
- }
122
-
123
- if (n.type === AST_NODE_TYPES.UpdateExpression) {
124
- if (n.argument.type === AST_NODE_TYPES.Identifier) {
125
- reportIfOuter(n.argument)
126
- }
127
- if (n.argument.type === AST_NODE_TYPES.MemberExpression && n.argument.object.type === AST_NODE_TYPES.ThisExpression) {
128
- context.report({ node: n.argument, messageId: 'assignmentOutside' })
129
- }
130
- return
131
- }
132
-
133
- for (const child of sourceCode.visitorKeys[n.type] ?? []) {
134
- const val = (n as any)[child]
135
- if (Array.isArray(val)) {
136
- for (const item of val) {
137
- if (item && typeof item.type === 'string') visitor(item)
138
- }
139
- } else if (val && typeof val.type === 'string') {
140
- visitor(val)
141
- }
142
- }
143
- }
144
-
145
- visitor(callbackBody)
146
- },
147
- }
148
- },
149
- })
@@ -1,122 +0,0 @@
1
- import { RuleTester } from '@typescript-eslint/rule-tester'
2
- import { noTranslateInstant } from './no-translate-instant.rule'
3
-
4
- RuleTester.afterAll = afterAll
5
-
6
- const ruleTester = new RuleTester({
7
- languageOptions: {
8
- parser: require('@typescript-eslint/parser'),
9
- parserOptions: {
10
- ecmaVersion: 2022,
11
- sourceType: 'module',
12
- },
13
- },
14
- } as unknown as any)
15
-
16
- const messageId = 'noTranslateInstant'
17
-
18
- ruleTester.run('no-translate-instant', noTranslateInstant, {
19
- valid: [
20
- {
21
- filename: `${process.cwd()}/libs/angular-linter-rules/src/some.spec.ts`,
22
- code: `
23
- class A {
24
- constructor(private translate: any) {}
25
- test() {
26
- return this.translate.instant('KEY')
27
- }
28
- }
29
- `,
30
- },
31
- {
32
- filename: `${process.cwd()}/libs/angular-linter-rules/src/testing/test-helper.ts`,
33
- code: `
34
- const translateService: any = { instant: (k: string) => k }
35
- translateService.instant('KEY')
36
- `,
37
- },
38
- {
39
- filename: `${process.cwd()}/libs/angular-linter-rules/src/mocks/translate.mock.ts`,
40
- code: `
41
- const translate: any = { instant: (k: string) => k }
42
- translate.instant('KEY')
43
- `,
44
- },
45
- {
46
- filename: `${process.cwd()}/libs/angular-linter-rules/src/app/a.ts`,
47
- code: `
48
- const other = { instant: (k: string) => k }
49
- other.instant('KEY')
50
- `,
51
- },
52
- ],
53
- invalid: [
54
- {
55
- filename: `${process.cwd()}/libs/angular-linter-rules/src/app/a.ts`,
56
- code: `
57
- class A {
58
- constructor(private translate: any) {}
59
- test() {
60
- return this.translate.instant('KEY')
61
- }
62
- }
63
- `,
64
- errors: [{ messageId }],
65
- },
66
- {
67
- filename: `${process.cwd()}/libs/angular-linter-rules/src/app/b.ts`,
68
- code: `
69
- function f(translate: any) {
70
- return translate.instant('KEY')
71
- }
72
- `,
73
- errors: [{ messageId }],
74
- },
75
- {
76
- filename: `${process.cwd()}/libs/angular-linter-rules/src/app/c.ts`,
77
- code: `
78
- function f(translateService: any) {
79
- return translateService.instant('KEY')
80
- }
81
- `,
82
- errors: [{ messageId }],
83
- },
84
- {
85
- filename: `${process.cwd()}/libs/angular-linter-rules/src/app/d.ts`,
86
- code: `
87
- class A {
88
- private translateService: any
89
- test() {
90
- return this.translateService.instant('KEY')
91
- }
92
- }
93
- `,
94
- errors: [{ messageId }],
95
- },
96
- {
97
- filename: `${process.cwd()}/libs/angular-linter-rules/src/app/e.ts`,
98
- code: `
99
- import { TranslateService } from '@ngx-translate/core'
100
-
101
- class A {
102
- constructor(private translate: TranslateService) {}
103
- test() {
104
- return this.translate.instant('KEY')
105
- }
106
- }
107
- `,
108
- errors: [{ messageId }],
109
- },
110
- {
111
- filename: `${process.cwd()}/libs/angular-linter-rules/src/app/f.ts`,
112
- code: `
113
- import { TranslateService } from '@ngx-translate/core'
114
-
115
- function f(translateService: TranslateService) {
116
- return translateService.instant('KEY')
117
- }
118
- `,
119
- errors: [{ messageId }],
120
- },
121
- ],
122
- })
@@ -1,98 +0,0 @@
1
- import { AST_NODE_TYPES, ESLintUtils, TSESTree } from '@typescript-eslint/utils'
2
- import { isTranslateServiceType } from '../utils/type-utils'
3
-
4
- const createRule = ESLintUtils.RuleCreator(
5
- (name) => `https://github.com/onecx/onecx-portal-ui-libs/tree/main/libs/angular-linter-rules#${name}`,
6
- )
7
-
8
- type Options = []
9
- type MessageIds = 'noTranslateInstant'
10
-
11
- const defaultAllowedFilePatterns = [/\.spec\.ts$/i, /\.test\.ts$/i, /\/testing\//i, /\/mocks\//i]
12
-
13
- function isAllowedTestFile(filename: string): boolean {
14
- if (!filename || filename === '<input>' || filename === '<text>') return false
15
- return defaultAllowedFilePatterns.some((re) => re.test(filename))
16
- }
17
-
18
- function isInstantMemberCall(node: TSESTree.CallExpression): boolean {
19
- if (node.callee.type !== AST_NODE_TYPES.MemberExpression) return false
20
- const property = node.callee.property
21
- if (property.type !== AST_NODE_TYPES.Identifier || property.name !== 'instant') return false
22
-
23
- const obj = node.callee.object
24
- if (obj.type === AST_NODE_TYPES.Identifier) {
25
- return obj.name === 'translate' || obj.name === 'translateService'
26
- }
27
-
28
- if (obj.type === AST_NODE_TYPES.MemberExpression) {
29
- if (obj.object.type !== AST_NODE_TYPES.ThisExpression) return false
30
- if (obj.property.type !== AST_NODE_TYPES.Identifier) return false
31
- return obj.property.name === 'translate' || obj.property.name === 'translateService'
32
- }
33
-
34
- return false
35
- }
36
-
37
- type ContextWithTypeInfo = {
38
- sourceCode: {
39
- parserServices?: {
40
- program?: unknown
41
- esTreeNodeToTSNodeMap?: unknown
42
- }
43
- }
44
- }
45
-
46
- function isTranslateServiceReceiver(context: ContextWithTypeInfo, node: TSESTree.CallExpression): boolean {
47
- if (node.callee.type !== AST_NODE_TYPES.MemberExpression) return false
48
- if (!context.sourceCode.parserServices?.program) return false
49
-
50
- const { esTreeNodeToTSNodeMap, program } = context.sourceCode.parserServices
51
- if (!esTreeNodeToTSNodeMap) return false
52
- const checker = (program as any).getTypeChecker()
53
-
54
- const receiverTsNode = (esTreeNodeToTSNodeMap as any).get(node.callee.object as any)
55
- const receiverType = checker.getTypeAtLocation(receiverTsNode)
56
- if (isTranslateServiceType(checker, receiverType)) return true
57
-
58
- const receiverTypeText = checker.typeToString(receiverType)
59
- return receiverTypeText === 'TranslateService'
60
- }
61
-
62
- export const noTranslateInstant = createRule<Options, MessageIds>({
63
- name: 'no-translate-instant',
64
- meta: {
65
- type: 'problem',
66
- docs: {
67
- description: 'Disallow ngx-translate TranslateService.instant outside of tests.',
68
- },
69
- schema: [],
70
- messages: {
71
- noTranslateInstant:
72
- 'Avoid `TranslateService.instant(...)` in production code. Use stream-based translation (`get`/`stream`) or the translate pipe instead.',
73
- },
74
- },
75
- defaultOptions: [],
76
- create(context) {
77
- const filename = context.filename
78
- const allowed = isAllowedTestFile(filename)
79
-
80
- return {
81
- CallExpression(node) {
82
- if (allowed) return
83
-
84
- if (isTranslateServiceReceiver(context as unknown as ContextWithTypeInfo, node)) {
85
- context.report({ node: node.callee, messageId: 'noTranslateInstant' })
86
- return
87
- }
88
-
89
- if (!isInstantMemberCall(node)) return
90
-
91
- context.report({
92
- node: node.callee,
93
- messageId: 'noTranslateInstant',
94
- })
95
- },
96
- }
97
- },
98
- })
@@ -1,97 +0,0 @@
1
- import { RuleTester } from '@typescript-eslint/rule-tester'
2
- import { preferTranslateParams } from './prefer-translate-params.rule'
3
-
4
- RuleTester.afterAll = afterAll
5
-
6
- const ruleTester = new RuleTester({
7
- languageOptions: {
8
- parser: require('@typescript-eslint/parser'),
9
- parserOptions: {
10
- ecmaVersion: 2022,
11
- sourceType: 'module',
12
- },
13
- },
14
- } as unknown as any)
15
-
16
- const messageId = 'preferTranslateParams'
17
-
18
- ruleTester.run('prefer-translate-params', preferTranslateParams, {
19
- valid: [
20
- {
21
- filename: `${process.cwd()}/libs/angular-linter-rules/src/app/a.ts`,
22
- code: `
23
- function f(translate: any, name: string) {
24
- return translate.get('HELLO_NAME', { name })
25
- }
26
- `,
27
- },
28
- {
29
- filename: `${process.cwd()}/libs/angular-linter-rules/src/app/b.ts`,
30
- code: `
31
- function f(translate: any, name: string) {
32
- return translate.get('HELLO_' + name)
33
- }
34
- `,
35
- },
36
- {
37
- filename: `${process.cwd()}/libs/angular-linter-rules/src/app/b2.ts`,
38
- code: `
39
- function f(translate: any, name: string) {
40
- return translate.stream('HELLO_' + name)
41
- }
42
- `,
43
- },
44
- {
45
- filename: `${process.cwd()}/libs/angular-linter-rules/src/some.spec.ts`,
46
- code: `
47
- function f(translate: any, name: string) {
48
- return translate.get('HELLO')
49
- }
50
- `,
51
- },
52
- ],
53
- invalid: [
54
- {
55
- filename: `${process.cwd()}/libs/angular-linter-rules/src/app/d.ts`,
56
- code: `
57
- import { map } from 'rxjs'
58
-
59
- function f(translateService: any, name: string) {
60
- return translate.get('HELLO').pipe(map((t: string) => 'Hi ' + t))
61
- }
62
- `,
63
- errors: [{ messageId }],
64
- },
65
- {
66
- filename: `${process.cwd()}/libs/angular-linter-rules/src/app/rx.ts`,
67
- code: `
68
- import { map } from 'rxjs'
69
-
70
- function f(translate: any, name: string) {
71
- return translate.get('HELLO').pipe(map((t: string) => t + name))
72
- }
73
- `,
74
- errors: [{ messageId }],
75
- },
76
- {
77
- filename: `${process.cwd()}/libs/angular-linter-rules/src/app/rx2.ts`,
78
- code: `
79
- import { map } from 'rxjs'
80
-
81
- function f(translate: any, name: string) {
82
- return translate.stream('HELLO').pipe(map((t: string) => t + name))
83
- }
84
- `,
85
- errors: [{ messageId }],
86
- },
87
- {
88
- filename: `${process.cwd()}/libs/angular-linter-rules/src/app/e.ts`,
89
- code: `
90
- function f(translate: any, name: string) {
91
- return ` + "`" + '${translate.instant(\'HELLO\')} ${name}' + "`" + `
92
- }
93
- `,
94
- errors: [{ messageId }],
95
- },
96
- ],
97
- })