@eduardbar/drift 1.2.0 → 1.3.0

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 (61) hide show
  1. package/.github/workflows/publish-vscode.yml +3 -3
  2. package/.github/workflows/publish.yml +3 -3
  3. package/.github/workflows/review-pr.yml +98 -6
  4. package/AGENTS.md +6 -0
  5. package/README.md +160 -10
  6. package/ROADMAP.md +6 -5
  7. package/dist/analyzer.d.ts +2 -2
  8. package/dist/analyzer.js +420 -159
  9. package/dist/benchmark.d.ts +2 -0
  10. package/dist/benchmark.js +185 -0
  11. package/dist/cli.js +453 -62
  12. package/dist/diff.js +74 -10
  13. package/dist/git.js +12 -0
  14. package/dist/index.d.ts +5 -3
  15. package/dist/index.js +3 -1
  16. package/dist/plugins.d.ts +2 -1
  17. package/dist/plugins.js +177 -28
  18. package/dist/printer.js +4 -0
  19. package/dist/review.js +2 -2
  20. package/dist/rules/comments.js +2 -2
  21. package/dist/rules/complexity.js +2 -7
  22. package/dist/rules/nesting.js +3 -13
  23. package/dist/rules/phase0-basic.js +10 -10
  24. package/dist/rules/shared.d.ts +2 -0
  25. package/dist/rules/shared.js +27 -3
  26. package/dist/saas.d.ts +143 -7
  27. package/dist/saas.js +478 -37
  28. package/dist/trust-kpi.d.ts +9 -0
  29. package/dist/trust-kpi.js +445 -0
  30. package/dist/trust.d.ts +65 -0
  31. package/dist/trust.js +571 -0
  32. package/dist/types.d.ts +154 -0
  33. package/docs/PRD.md +187 -109
  34. package/docs/plugin-contract.md +61 -0
  35. package/docs/trust-core-release-checklist.md +55 -0
  36. package/package.json +5 -3
  37. package/src/analyzer.ts +484 -155
  38. package/src/benchmark.ts +244 -0
  39. package/src/cli.ts +562 -79
  40. package/src/diff.ts +75 -10
  41. package/src/git.ts +16 -0
  42. package/src/index.ts +48 -0
  43. package/src/plugins.ts +354 -26
  44. package/src/printer.ts +4 -0
  45. package/src/review.ts +2 -2
  46. package/src/rules/comments.ts +2 -2
  47. package/src/rules/complexity.ts +2 -7
  48. package/src/rules/nesting.ts +3 -13
  49. package/src/rules/phase0-basic.ts +11 -12
  50. package/src/rules/shared.ts +31 -3
  51. package/src/saas.ts +641 -43
  52. package/src/trust-kpi.ts +518 -0
  53. package/src/trust.ts +774 -0
  54. package/src/types.ts +171 -0
  55. package/tests/diff.test.ts +124 -0
  56. package/tests/new-features.test.ts +71 -0
  57. package/tests/plugins.test.ts +219 -0
  58. package/tests/rules.test.ts +23 -1
  59. package/tests/saas-foundation.test.ts +358 -1
  60. package/tests/trust-kpi.test.ts +120 -0
  61. package/tests/trust.test.ts +584 -0
@@ -1,6 +1,6 @@
1
1
  import { SourceFile, SyntaxKind } from 'ts-morph'
2
2
  import type { DriftIssue } from '../types.js'
3
- import { hasIgnoreComment, getSnippet, type FunctionLike } from './shared.js'
3
+ import { hasIgnoreComment, getSnippet, collectFunctionLikes, type FunctionLike } from './shared.js'
4
4
 
5
5
  const COMPLEXITY_THRESHOLD = 10
6
6
 
@@ -31,12 +31,7 @@ function getCyclomaticComplexity(fn: FunctionLike): number {
31
31
 
32
32
  export function detectHighComplexity(file: SourceFile): DriftIssue[] {
33
33
  const issues: DriftIssue[] = []
34
- const fns: FunctionLike[] = [
35
- ...file.getFunctions(),
36
- ...file.getDescendantsOfKind(SyntaxKind.ArrowFunction),
37
- ...file.getDescendantsOfKind(SyntaxKind.FunctionExpression),
38
- ...file.getClasses().flatMap((c) => c.getMethods()),
39
- ]
34
+ const fns: FunctionLike[] = collectFunctionLikes(file)
40
35
 
41
36
  for (const fn of fns) {
42
37
  const complexity = getCyclomaticComplexity(fn)
@@ -1,6 +1,6 @@
1
1
  import { SourceFile, SyntaxKind, Node } from 'ts-morph'
2
2
  import type { DriftIssue } from '../types.js'
3
- import { hasIgnoreComment, getSnippet, type FunctionLike } from './shared.js'
3
+ import { hasIgnoreComment, getSnippet, collectFunctionLikes, type FunctionLike } from './shared.js'
4
4
 
5
5
  const NESTING_THRESHOLD = 3
6
6
  const PARAMS_THRESHOLD = 4
@@ -35,12 +35,7 @@ function getMaxNestingDepth(fn: FunctionLike): number {
35
35
 
36
36
  export function detectDeepNesting(file: SourceFile): DriftIssue[] {
37
37
  const issues: DriftIssue[] = []
38
- const fns: FunctionLike[] = [
39
- ...file.getFunctions(),
40
- ...file.getDescendantsOfKind(SyntaxKind.ArrowFunction),
41
- ...file.getDescendantsOfKind(SyntaxKind.FunctionExpression),
42
- ...file.getClasses().flatMap((c) => c.getMethods()),
43
- ]
38
+ const fns: FunctionLike[] = collectFunctionLikes(file)
44
39
 
45
40
  for (const fn of fns) {
46
41
  const depth = getMaxNestingDepth(fn)
@@ -62,12 +57,7 @@ export function detectDeepNesting(file: SourceFile): DriftIssue[] {
62
57
 
63
58
  export function detectTooManyParams(file: SourceFile): DriftIssue[] {
64
59
  const issues: DriftIssue[] = []
65
- const fns: FunctionLike[] = [
66
- ...file.getFunctions(),
67
- ...file.getDescendantsOfKind(SyntaxKind.ArrowFunction),
68
- ...file.getDescendantsOfKind(SyntaxKind.FunctionExpression),
69
- ...file.getClasses().flatMap((c) => c.getMethods()),
70
- ]
60
+ const fns: FunctionLike[] = collectFunctionLikes(file)
71
61
 
72
62
  for (const fn of fns) {
73
63
  const paramCount = fn.getParameters().length
@@ -1,6 +1,6 @@
1
1
  import { SourceFile, SyntaxKind } from 'ts-morph'
2
2
  import type { DriftIssue } from '../types.js'
3
- import { hasIgnoreComment, getSnippet, getFunctionLikeLines, type FunctionLike } from './shared.js'
3
+ import { hasIgnoreComment, getSnippet, getFunctionLikeLines, collectFunctionLikes, getFileLines } from './shared.js'
4
4
 
5
5
  const LARGE_FILE_THRESHOLD = 300
6
6
  const LARGE_FUNCTION_THRESHOLD = 50
@@ -26,12 +26,7 @@ export function detectLargeFile(file: SourceFile): DriftIssue[] {
26
26
 
27
27
  export function detectLargeFunctions(file: SourceFile): DriftIssue[] {
28
28
  const issues: DriftIssue[] = []
29
- const fns: FunctionLike[] = [
30
- ...file.getFunctions(),
31
- ...file.getDescendantsOfKind(SyntaxKind.ArrowFunction),
32
- ...file.getDescendantsOfKind(SyntaxKind.FunctionExpression),
33
- ...file.getClasses().flatMap((c) => c.getMethods()),
34
- ]
29
+ const fns = collectFunctionLikes(file)
35
30
 
36
31
  for (const fn of fns) {
37
32
  const lines = getFunctionLikeLines(fn)
@@ -70,7 +65,7 @@ export function detectDebugLeftovers(file: SourceFile): DriftIssue[] {
70
65
  }
71
66
  }
72
67
 
73
- const lines = file.getFullText().split('\n')
68
+ const lines = getFileLines(file)
74
69
  lines.forEach((lineContent, i) => {
75
70
  if (/\/\/\s*(TODO|FIXME|HACK|XXX|TEMP)\b/i.test(lineContent)) {
76
71
  if (hasIgnoreComment(file, i + 1)) return
@@ -90,14 +85,18 @@ export function detectDebugLeftovers(file: SourceFile): DriftIssue[] {
90
85
 
91
86
  export function detectDeadCode(file: SourceFile): DriftIssue[] {
92
87
  const issues: DriftIssue[] = []
88
+ const identifierCounts = new Map<string, number>()
89
+
90
+ for (const id of file.getDescendantsOfKind(SyntaxKind.Identifier)) {
91
+ const text = id.getText()
92
+ identifierCounts.set(text, (identifierCounts.get(text) ?? 0) + 1)
93
+ }
93
94
 
94
95
  for (const imp of file.getImportDeclarations()) {
95
96
  for (const named of imp.getNamedImports()) {
96
97
  const name = named.getName()
97
- const refs = file.getDescendantsOfKind(SyntaxKind.Identifier).filter(
98
- (id) => id.getText() === name && id !== named.getNameNode()
99
- )
100
- if (refs.length === 0) {
98
+ const refsCount = Math.max(0, (identifierCounts.get(name) ?? 0) - 1)
99
+ if (refsCount === 0) {
101
100
  issues.push({
102
101
  rule: 'dead-code',
103
102
  severity: 'warning',
@@ -5,12 +5,40 @@ import {
5
5
  ArrowFunction,
6
6
  FunctionExpression,
7
7
  MethodDeclaration,
8
+ SyntaxKind,
8
9
  } from 'ts-morph'
9
10
 
10
11
  export type FunctionLike = FunctionDeclaration | ArrowFunction | FunctionExpression | MethodDeclaration
11
12
 
12
- export function hasIgnoreComment(file: SourceFile, line: number): boolean {
13
+ const fileLinesCache = new WeakMap<SourceFile, string[]>()
14
+ const functionLikesCache = new WeakMap<SourceFile, FunctionLike[]>()
15
+
16
+ export function getFileLines(file: SourceFile): string[] {
17
+ const cached = fileLinesCache.get(file)
18
+ if (cached) return cached
19
+
13
20
  const lines = file.getFullText().split('\n')
21
+ fileLinesCache.set(file, lines)
22
+ return lines
23
+ }
24
+
25
+ export function collectFunctionLikes(file: SourceFile): FunctionLike[] {
26
+ const cached = functionLikesCache.get(file)
27
+ if (cached) return cached
28
+
29
+ const fns: FunctionLike[] = [
30
+ ...file.getFunctions(),
31
+ ...file.getDescendantsOfKind(SyntaxKind.ArrowFunction),
32
+ ...file.getDescendantsOfKind(SyntaxKind.FunctionExpression),
33
+ ...file.getClasses().flatMap((c) => c.getMethods()),
34
+ ]
35
+
36
+ functionLikesCache.set(file, fns)
37
+ return fns
38
+ }
39
+
40
+ export function hasIgnoreComment(file: SourceFile, line: number): boolean {
41
+ const lines = getFileLines(file)
14
42
  const currentLine = lines[line - 1] ?? ''
15
43
  const prevLine = lines[line - 2] ?? ''
16
44
 
@@ -20,13 +48,13 @@ export function hasIgnoreComment(file: SourceFile, line: number): boolean {
20
48
  }
21
49
 
22
50
  export function isFileIgnored(file: SourceFile): boolean {
23
- const firstLines = file.getFullText().split('\n').slice(0, 10).join('\n') // drift-ignore
51
+ const firstLines = getFileLines(file).slice(0, 10).join('\n') // drift-ignore
24
52
  return /\/\/\s*drift-ignore-file\b/.test(firstLines)
25
53
  }
26
54
 
27
55
  export function getSnippet(node: Node, file: SourceFile): string {
28
56
  const startLine = node.getStartLineNumber()
29
- const lines = file.getFullText().split('\n')
57
+ const lines = getFileLines(file)
30
58
  return lines
31
59
  .slice(Math.max(0, startLine - 1), startLine + 1)
32
60
  .join('\n')