vouchington-tooling 0.32.0 → 0.34.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 (208) hide show
  1. package/README.md +219 -1
  2. package/dist/api-contract-discovery/index.d.mts +4 -1
  3. package/dist/api-contract-discovery/index.mjs +1 -0
  4. package/dist/api-contract-discovery/protocol-analysis-cache.d.mts +21 -0
  5. package/dist/api-contract-discovery/protocol-analysis-cache.mjs +7 -0
  6. package/dist/api-contract-discovery/protocol-callback-invocation.d.mts +2 -1
  7. package/dist/api-contract-discovery/protocol-callback-invocation.mjs +12 -4
  8. package/dist/api-contract-discovery/protocol-callback-values.d.mts +2 -1
  9. package/dist/api-contract-discovery/protocol-callback-values.mjs +3 -3
  10. package/dist/api-contract-discovery/protocol-execution-path.d.mts +2 -1
  11. package/dist/api-contract-discovery/protocol-execution-path.mjs +2 -2
  12. package/dist/api-contract-discovery/protocol-http-association.mjs +8 -0
  13. package/dist/api-contract-discovery/protocol-http-context-callers.d.mts +2 -1
  14. package/dist/api-contract-discovery/protocol-http-context-callers.mjs +13 -7
  15. package/dist/api-contract-discovery/protocol-http-context-escapes.d.mts +3 -0
  16. package/dist/api-contract-discovery/protocol-http-context-escapes.mjs +95 -0
  17. package/dist/api-contract-discovery/protocol-http-context.d.mts +1 -2
  18. package/dist/api-contract-discovery/protocol-http-context.mjs +6 -29
  19. package/dist/api-contract-discovery/protocol-http-method-access.d.mts +9 -0
  20. package/dist/api-contract-discovery/protocol-http-method-access.mjs +34 -0
  21. package/dist/api-contract-discovery/protocol-http-reflect.d.mts +5 -0
  22. package/dist/api-contract-discovery/protocol-http-reflect.mjs +45 -0
  23. package/dist/api-contract-discovery/protocol-http-response-mutations.d.mts +3 -0
  24. package/dist/api-contract-discovery/protocol-http-response-mutations.mjs +31 -0
  25. package/dist/api-contract-discovery/protocol-platform-callbacks.d.mts +2 -0
  26. package/dist/api-contract-discovery/protocol-platform-callbacks.mjs +4 -0
  27. package/dist/api-contract-discovery/protocol-sse-raw-writes.mjs +36 -7
  28. package/dist/api-contract-discovery/protocol-sse-write-helpers.d.mts +1 -0
  29. package/dist/api-contract-discovery/protocol-sse-write-helpers.mjs +21 -5
  30. package/dist/api-contract-discovery/protocol-sse-write-mutations.d.mts +1 -1
  31. package/dist/api-contract-discovery/protocol-sse-write-mutations.mjs +8 -2
  32. package/dist/api-contract-discovery/query-contract-extraction.mjs +20 -3
  33. package/dist/api-contract-discovery/query-contract-literals.d.mts +5 -0
  34. package/dist/api-contract-discovery/query-contract-literals.mjs +29 -0
  35. package/dist/api-contract-discovery/registered-route-binding-writes.d.mts +2 -1
  36. package/dist/api-contract-discovery/registered-route-binding-writes.mjs +20 -11
  37. package/dist/api-contract-discovery/registered-route-handler-analysis.d.mts +7 -1
  38. package/dist/api-contract-discovery/registered-route-handler-analysis.mjs +27 -26
  39. package/dist/api-contract-discovery/registered-route-handler-helpers.d.mts +3 -0
  40. package/dist/api-contract-discovery/registered-route-handler-helpers.mjs +16 -0
  41. package/dist/api-contract-discovery/request-validation-callbacks.d.mts +9 -0
  42. package/dist/api-contract-discovery/request-validation-callbacks.mjs +27 -0
  43. package/dist/api-contract-discovery/request-validation-checker.d.mts +7 -0
  44. package/dist/api-contract-discovery/request-validation-checker.mjs +25 -0
  45. package/dist/api-contract-discovery/request-validation-conditional.d.mts +6 -0
  46. package/dist/api-contract-discovery/request-validation-conditional.mjs +31 -0
  47. package/dist/api-contract-discovery/request-validation-factory-calls.d.mts +11 -0
  48. package/dist/api-contract-discovery/request-validation-factory-calls.mjs +23 -0
  49. package/dist/api-contract-discovery/request-validation-facts.d.mts +16 -0
  50. package/dist/api-contract-discovery/request-validation-facts.mjs +64 -0
  51. package/dist/api-contract-discovery/request-validation-follow.d.mts +24 -0
  52. package/dist/api-contract-discovery/request-validation-follow.mjs +38 -0
  53. package/dist/api-contract-discovery/request-validation-handler-state.d.mts +8 -0
  54. package/dist/api-contract-discovery/request-validation-handler-state.mjs +35 -0
  55. package/dist/api-contract-discovery/request-validation-input.d.mts +12 -0
  56. package/dist/api-contract-discovery/request-validation-input.mjs +43 -0
  57. package/dist/api-contract-discovery/request-validation-keys.d.mts +25 -0
  58. package/dist/api-contract-discovery/request-validation-keys.mjs +119 -0
  59. package/dist/api-contract-discovery/request-validation-match.d.mts +11 -0
  60. package/dist/api-contract-discovery/request-validation-match.mjs +47 -0
  61. package/dist/api-contract-discovery/request-validation-mutation.d.mts +7 -0
  62. package/dist/api-contract-discovery/request-validation-mutation.mjs +80 -0
  63. package/dist/api-contract-discovery/request-validation-origin.d.mts +20 -0
  64. package/dist/api-contract-discovery/request-validation-origin.mjs +71 -0
  65. package/dist/api-contract-discovery/request-validation-reads.d.mts +13 -0
  66. package/dist/api-contract-discovery/request-validation-reads.mjs +88 -0
  67. package/dist/api-contract-discovery/request-validation-registration.d.mts +9 -0
  68. package/dist/api-contract-discovery/request-validation-registration.mjs +32 -0
  69. package/dist/api-contract-discovery/request-validation-report.d.mts +3 -0
  70. package/dist/api-contract-discovery/request-validation-report.mjs +21 -0
  71. package/dist/api-contract-discovery/request-validation-request-origin.d.mts +5 -0
  72. package/dist/api-contract-discovery/request-validation-request-origin.mjs +13 -0
  73. package/dist/api-contract-discovery/request-validation-select.d.mts +9 -0
  74. package/dist/api-contract-discovery/request-validation-select.mjs +36 -0
  75. package/dist/api-contract-discovery/request-validation-sites.d.mts +9 -0
  76. package/dist/api-contract-discovery/request-validation-sites.mjs +77 -0
  77. package/dist/api-contract-discovery/request-validation-state-key.d.mts +13 -0
  78. package/dist/api-contract-discovery/request-validation-state-key.mjs +30 -0
  79. package/dist/api-contract-discovery/request-validation-trace-helpers.d.mts +14 -0
  80. package/dist/api-contract-discovery/request-validation-trace-helpers.mjs +80 -0
  81. package/dist/api-contract-discovery/request-validation-trace-select.d.mts +23 -0
  82. package/dist/api-contract-discovery/request-validation-trace-select.mjs +45 -0
  83. package/dist/api-contract-discovery/request-validation-trace.d.mts +8 -0
  84. package/dist/api-contract-discovery/request-validation-trace.mjs +130 -0
  85. package/dist/api-contract-discovery/request-validation-types.d.mts +84 -0
  86. package/dist/api-contract-discovery/request-validation-types.mjs +1 -0
  87. package/dist/api-contract-discovery/request-validation-walker.d.mts +26 -0
  88. package/dist/api-contract-discovery/request-validation-walker.mjs +143 -0
  89. package/dist/api-contract-discovery/request-validation.test-helpers.d.mts +39 -0
  90. package/dist/api-contract-discovery/request-validation.test-helpers.mjs +108 -0
  91. package/dist/api-contract-discovery/response-contract-attribution-facts.d.mts +2 -0
  92. package/dist/api-contract-discovery/response-contract-attribution-facts.mjs +18 -0
  93. package/dist/api-contract-discovery/response-contract-attribution.d.mts +3 -0
  94. package/dist/api-contract-discovery/response-contract-attribution.mjs +23 -0
  95. package/dist/api-contract-discovery/response-contract-call-classification.d.mts +3 -0
  96. package/dist/api-contract-discovery/response-contract-call-classification.mjs +37 -0
  97. package/dist/api-contract-discovery/response-contract-error-branch.d.mts +1 -1
  98. package/dist/api-contract-discovery/response-contract-error-branch.mjs +109 -6
  99. package/dist/api-contract-discovery/response-contract-handler-bindings.d.mts +3 -0
  100. package/dist/api-contract-discovery/response-contract-handler-bindings.mjs +126 -0
  101. package/dist/api-contract-discovery/response-contract-handler-calls.d.mts +3 -0
  102. package/dist/api-contract-discovery/response-contract-handler-calls.mjs +81 -0
  103. package/dist/api-contract-discovery/response-contract-implicit-variants.d.mts +2 -0
  104. package/dist/api-contract-discovery/response-contract-implicit-variants.mjs +6 -0
  105. package/dist/api-contract-discovery/response-contract-implicit.d.mts +2 -1
  106. package/dist/api-contract-discovery/response-contract-implicit.mjs +9 -17
  107. package/dist/api-contract-discovery/response-contract-lenient.d.mts +18 -0
  108. package/dist/api-contract-discovery/response-contract-object-symbols.d.mts +3 -0
  109. package/dist/api-contract-discovery/response-contract-object-symbols.mjs +80 -0
  110. package/dist/api-contract-discovery/response-contract-property-mutations.d.mts +5 -0
  111. package/dist/api-contract-discovery/response-contract-property-mutations.mjs +84 -0
  112. package/dist/api-contract-discovery/response-contract-registration.d.mts +1 -1
  113. package/dist/api-contract-discovery/response-contract-registration.mjs +7 -3
  114. package/dist/api-contract-discovery/response-contract-registry.mjs +43 -8
  115. package/dist/api-contract-discovery/response-contract-route-analysis.d.mts +3 -4
  116. package/dist/api-contract-discovery/response-contract-route-analysis.mjs +4 -101
  117. package/dist/api-contract-discovery/response-contract-route-syntax.d.mts +7 -0
  118. package/dist/api-contract-discovery/response-contract-route-syntax.mjs +41 -0
  119. package/dist/api-contract-discovery/response-contract-symbols.d.mts +5 -0
  120. package/dist/api-contract-discovery/response-contract-symbols.mjs +82 -0
  121. package/dist/basic-auth-doc-sync/index.d.mts +3 -0
  122. package/dist/basic-auth-doc-sync/index.mjs +2 -0
  123. package/dist/basic-auth-doc-sync/methods.d.mts +1 -0
  124. package/dist/basic-auth-doc-sync/methods.mjs +4 -0
  125. package/dist/basic-auth-doc-sync/runbook.d.mts +5 -0
  126. package/dist/basic-auth-doc-sync/runbook.mjs +59 -0
  127. package/dist/basic-auth-doc-sync/source.d.mts +2 -0
  128. package/dist/basic-auth-doc-sync/source.mjs +71 -0
  129. package/dist/compiler-build/index.d.mts +2 -0
  130. package/dist/compiler-build/index.mjs +1 -0
  131. package/dist/compiler-build/program-configuration.d.mts +9 -0
  132. package/dist/compiler-build/program-configuration.mjs +28 -0
  133. package/dist/compiler-build/program-types.d.mts +29 -0
  134. package/dist/compiler-build/program-types.mjs +1 -0
  135. package/dist/compiler-build/program.d.mts +7 -0
  136. package/dist/compiler-build/program.mjs +78 -0
  137. package/dist/contract-schema/index.d.mts +4 -0
  138. package/dist/contract-schema/index.mjs +2 -0
  139. package/dist/contract-schema/type-query-api.d.mts +44 -0
  140. package/dist/contract-schema/type-query-api.mjs +10 -0
  141. package/dist/contract-schema/type-query-call.d.mts +16 -0
  142. package/dist/contract-schema/type-query-call.mjs +52 -0
  143. package/dist/contract-schema/type-query-export.d.mts +10 -0
  144. package/dist/contract-schema/type-query-export.mjs +91 -0
  145. package/dist/contract-schema/type-query.d.mts +22 -0
  146. package/dist/contract-schema/type-query.mjs +39 -0
  147. package/dist/finite-enum-ripple/ast.d.mts +9 -0
  148. package/dist/finite-enum-ripple/ast.mjs +94 -0
  149. package/dist/finite-enum-ripple/check.d.mts +3 -0
  150. package/dist/finite-enum-ripple/check.mjs +62 -0
  151. package/dist/finite-enum-ripple/compare.d.mts +20 -0
  152. package/dist/finite-enum-ripple/compare.mjs +90 -0
  153. package/dist/finite-enum-ripple/component-paths.d.mts +8 -0
  154. package/dist/finite-enum-ripple/component-paths.mjs +30 -0
  155. package/dist/finite-enum-ripple/configured-object-mutations.d.mts +1 -0
  156. package/dist/finite-enum-ripple/configured-object-mutations.mjs +78 -0
  157. package/dist/finite-enum-ripple/create-page-jsx.d.mts +3 -0
  158. package/dist/finite-enum-ripple/create-page-jsx.mjs +28 -0
  159. package/dist/finite-enum-ripple/create-page-literals.d.mts +2 -0
  160. package/dist/finite-enum-ripple/create-page-literals.mjs +83 -0
  161. package/dist/finite-enum-ripple/create-page-object-visitor.d.mts +2 -0
  162. package/dist/finite-enum-ripple/create-page-object-visitor.mjs +57 -0
  163. package/dist/finite-enum-ripple/create-page-properties.d.mts +4 -0
  164. package/dist/finite-enum-ripple/create-page-properties.mjs +35 -0
  165. package/dist/finite-enum-ripple/index.d.mts +2 -0
  166. package/dist/finite-enum-ripple/index.mjs +1 -0
  167. package/dist/finite-enum-ripple/model.d.mts +74 -0
  168. package/dist/finite-enum-ripple/model.mjs +1 -0
  169. package/dist/finite-enum-ripple/parser-support.d.mts +11 -0
  170. package/dist/finite-enum-ripple/parser-support.mjs +86 -0
  171. package/dist/finite-enum-ripple/parsers.d.mts +32 -0
  172. package/dist/finite-enum-ripple/parsers.mjs +113 -0
  173. package/dist/finite-enum-ripple/pattern-captures.d.mts +2 -0
  174. package/dist/finite-enum-ripple/pattern-captures.mjs +45 -0
  175. package/dist/finite-enum-ripple/structured-route-check.d.mts +3 -0
  176. package/dist/finite-enum-ripple/structured-route-check.mjs +94 -0
  177. package/dist/finite-enum-ripple/types.d.mts +2 -0
  178. package/dist/finite-enum-ripple/types.mjs +60 -0
  179. package/dist/finite-enum-ripple/union-factory-check.d.mts +2 -0
  180. package/dist/finite-enum-ripple/union-factory-check.mjs +34 -0
  181. package/dist/finite-enum-ripple/union-types.d.mts +2 -0
  182. package/dist/finite-enum-ripple/union-types.mjs +129 -0
  183. package/dist/markdown/content-facts.d.mts +6 -0
  184. package/dist/markdown/content-facts.mjs +18 -0
  185. package/dist/markdown/html-blocks.d.mts +5 -0
  186. package/dist/markdown/html-blocks.mjs +51 -0
  187. package/dist/markdown/index.d.mts +4 -1
  188. package/dist/markdown/index.mjs +3 -0
  189. package/dist/markdown/tasks.d.mts +2 -0
  190. package/dist/markdown/tasks.mjs +10 -0
  191. package/dist/markdown/types.d.mts +27 -0
  192. package/dist/openapi-document/build-openapi-query.mjs +1 -1
  193. package/dist/openapi-document/openapi-types.d.mts +1 -1
  194. package/dist/openapi-document/query-types.d.mts +8 -2
  195. package/dist/post-publication-inventory/index.d.mts +5 -0
  196. package/dist/post-publication-inventory/index.mjs +2 -0
  197. package/dist/post-publication-inventory/readers/sql-executors.d.mts +3 -0
  198. package/dist/post-publication-inventory/readers/sql-executors.mjs +32 -0
  199. package/dist/post-publication-inventory/readers/sql-source.d.mts +7 -0
  200. package/dist/post-publication-inventory/readers/sql-source.mjs +87 -0
  201. package/dist/post-publication-inventory/readers/sql-template.d.mts +12 -0
  202. package/dist/post-publication-inventory/readers/sql-template.mjs +119 -0
  203. package/dist/post-publication-inventory/writers/source.d.mts +9 -0
  204. package/dist/post-publication-inventory/writers/source.mjs +128 -0
  205. package/dist/post-publication-inventory/writers/types.d.mts +19 -0
  206. package/dist/post-publication-inventory/writers/types.mjs +1 -0
  207. package/docs/basic-auth-doc-sync.md +37 -0
  208. package/package.json +24 -3
@@ -0,0 +1,10 @@
1
+ export function programForQuery(program) {
2
+ return program;
3
+ }
4
+ export function validateTypeScriptApi(typescript, program, sourceFile) {
5
+ if (!typescript.isSourceFile(sourceFile) ||
6
+ sourceFile.kind !== typescript.SyntaxKind.SourceFile) {
7
+ throw new Error('TypeScript API does not recognize the supplied Program source file; pass the same compiler API instance that created the Program');
8
+ }
9
+ program.getTypeChecker();
10
+ }
@@ -0,0 +1,16 @@
1
+ import { type TypeFacts, type TypeFactsRequest } from './type-query.mts';
2
+ import { type TypeScriptApi, type TypeScriptProgram } from './type-query-api.mts';
3
+ export interface CallRowTypeFactsRequest extends TypeFactsRequest {
4
+ readonly program: TypeScriptProgram;
5
+ readonly typescript: TypeScriptApi;
6
+ readonly fileName: string;
7
+ readonly calleeText: string;
8
+ readonly rowSource: 'typeArgument' | 'awaitedRows';
9
+ readonly typeArgumentIndex?: number;
10
+ readonly typeArgumentText?: string;
11
+ }
12
+ export interface CallRowTypeFacts extends TypeFacts {
13
+ readonly line: number;
14
+ readonly column: number;
15
+ }
16
+ export declare function getCallRowTypeFacts(request: CallRowTypeFactsRequest): CallRowTypeFacts[];
@@ -0,0 +1,52 @@
1
+ import { sourceFileForQuery } from './type-query-export.mjs';
2
+ import { factsForType } from './type-query.mjs';
3
+ import { programForQuery, validateTypeScriptApi, } from './type-query-api.mjs';
4
+ function rowType(typescript, checker, sourceFile, call, request) {
5
+ if (request.rowSource === 'typeArgument') {
6
+ const typeArgument = call.typeArguments?.[request.typeArgumentIndex ?? 0];
7
+ if (!typeArgument) {
8
+ throw new Error(`Missing row type argument for call "${request.calleeText}" in "${sourceFile.fileName}"`);
9
+ }
10
+ return { type: checker.getTypeFromTypeNode(typeArgument), at: typeArgument };
11
+ }
12
+ const result = checker.getAwaitedType(checker.getTypeAtLocation(call));
13
+ if (result && result.flags & (typescript.TypeFlags.Any | typescript.TypeFlags.Never))
14
+ return { type: result, at: call };
15
+ const rowsProperty = result && checker.getPropertyOfType(result, 'rows');
16
+ const rows = rowsProperty
17
+ ? checker.getTypeOfSymbolAtLocation(rowsProperty, call)
18
+ : result && checker.getIndexTypeOfType(result, typescript.IndexKind.String);
19
+ if (rows && rows.flags & (typescript.TypeFlags.Any | typescript.TypeFlags.Never)) {
20
+ return { type: rows, at: call };
21
+ }
22
+ const element = rows && checker.getIndexTypeOfType(rows, typescript.IndexKind.Number);
23
+ if (!element) {
24
+ throw new Error(`Missing awaited rows element for call "${request.calleeText}" in "${sourceFile.fileName}"`);
25
+ }
26
+ return { type: element, at: call };
27
+ }
28
+ export function getCallRowTypeFacts(request) {
29
+ const program = programForQuery(request.program);
30
+ const sourceFile = sourceFileForQuery(program, request.fileName);
31
+ validateTypeScriptApi(request.typescript, request.program, sourceFile);
32
+ const checker = program.getTypeChecker();
33
+ const facts = [];
34
+ function visit(node) {
35
+ if (request.typescript.isCallExpression(node) &&
36
+ node.expression.getText(sourceFile) === request.calleeText) {
37
+ const selectedText = node.typeArguments?.[request.typeArgumentIndex ?? 0]?.getText(sourceFile);
38
+ if (request.typeArgumentText === undefined || selectedText === request.typeArgumentText) {
39
+ const row = rowType(request.typescript, checker, sourceFile, node, request);
40
+ const location = sourceFile.getLineAndCharacterOfPosition(node.getStart(sourceFile));
41
+ facts.push({
42
+ line: location.line + 1,
43
+ column: location.character + 1,
44
+ ...factsForType(request.typescript, program, checker, row.type, row.at, request),
45
+ });
46
+ }
47
+ }
48
+ request.typescript.forEachChild(node, visit);
49
+ }
50
+ visit(sourceFile);
51
+ return facts;
52
+ }
@@ -0,0 +1,10 @@
1
+ import type ts from './typescript-api.mts';
2
+ import type { TypeScriptApi } from './type-query-api.mts';
3
+ export interface ExportedTypeSelector {
4
+ readonly fileName: string;
5
+ readonly exportName: string;
6
+ }
7
+ export declare function sourceFileForQuery(program: ts.Program, fileName: string): ts.SourceFile;
8
+ export declare function exportedType(typescript: TypeScriptApi, program: ts.Program, checker: ts.TypeChecker, selector: ExportedTypeSelector): ts.Type;
9
+ export declare function exportedAssignableType(typescript: TypeScriptApi, program: ts.Program, checker: ts.TypeChecker, selector: ExportedTypeSelector): ts.Type;
10
+ export declare function exportedDefaultType(typescript: TypeScriptApi, program: ts.Program, checker: ts.TypeChecker, selector: ExportedTypeSelector, parameterIndex: number): ts.Type;
@@ -0,0 +1,91 @@
1
+ export function sourceFileForQuery(program, fileName) {
2
+ const sourceFile = program.getSourceFile(fileName);
3
+ if (!sourceFile)
4
+ throw new Error(`Type-query source file "${fileName}" is not in the program`);
5
+ return sourceFile;
6
+ }
7
+ function exportedSymbol(typescript, checker, sourceFile, exportName) {
8
+ const moduleSymbol = checker.getSymbolAtLocation(sourceFile);
9
+ const exported = moduleSymbol &&
10
+ checker.getExportsOfModule(moduleSymbol).find((candidate) => candidate.name === exportName);
11
+ if (!exported) {
12
+ throw new Error(`Missing export "${exportName}" in "${sourceFile.fileName}"`);
13
+ }
14
+ return exported.flags & typescript.SymbolFlags.Alias
15
+ ? checker.getAliasedSymbol(exported)
16
+ : exported;
17
+ }
18
+ export function exportedType(typescript, program, checker, selector) {
19
+ const sourceFile = sourceFileForQuery(program, selector.fileName);
20
+ const symbol = exportedSymbol(typescript, checker, sourceFile, selector.exportName);
21
+ const declaration = symbol.valueDeclaration ?? symbol.declarations?.[0];
22
+ if (!declaration) {
23
+ throw new Error(`Export "${selector.exportName}" in "${selector.fileName}" could not be resolved to a declaration`);
24
+ }
25
+ if (symbol.flags & typescript.SymbolFlags.Type)
26
+ return checker.getDeclaredTypeOfSymbol(symbol);
27
+ return checker.getTypeOfSymbolAtLocation(symbol, declaration);
28
+ }
29
+ export function exportedAssignableType(typescript, program, checker, selector) {
30
+ const sourceFile = sourceFileForQuery(program, selector.fileName);
31
+ const symbol = exportedSymbol(typescript, checker, sourceFile, selector.exportName);
32
+ const genericDeclaration = symbol.declarations?.some((declaration) => {
33
+ const isGenericTypeDeclaration = typescript.isClassDeclaration(declaration) ||
34
+ typescript.isInterfaceDeclaration(declaration) ||
35
+ typescript.isTypeAliasDeclaration(declaration);
36
+ return (isGenericTypeDeclaration &&
37
+ Boolean(declaration.typeParameters?.length));
38
+ });
39
+ const type = exportedType(typescript, program, checker, selector);
40
+ const defaultedSignature = [...type.getCallSignatures(), ...type.getConstructSignatures()].some((signature) => signature.getDeclaration()?.typeParameters?.some((parameter) => parameter.default));
41
+ if (genericDeclaration ||
42
+ (!(symbol.flags & typescript.SymbolFlags.Value) && defaultedSignature)) {
43
+ throw new Error(`Assignable target "${selector.exportName}" in "${selector.fileName}" has generic or defaulted type parameters; export and select a named instantiated type instead`);
44
+ }
45
+ return type;
46
+ }
47
+ export function exportedDefaultType(typescript, program, checker, selector, parameterIndex) {
48
+ const sourceFile = sourceFileForQuery(program, selector.fileName);
49
+ const symbol = exportedSymbol(typescript, checker, sourceFile, selector.exportName);
50
+ const directDeclaration = symbol.declarations?.find((declaration) => !typescript.isFunctionDeclaration(declaration) &&
51
+ (typescript.isClassDeclaration(declaration) ||
52
+ typescript.isInterfaceDeclaration(declaration) ||
53
+ typescript.isTypeAliasDeclaration(declaration)));
54
+ const directParameters = directDeclaration?.typeParameters;
55
+ const type = exportedType(typescript, program, checker, selector);
56
+ const signatureParameters = [
57
+ ...type.getCallSignatures(),
58
+ ...type.getConstructSignatures(),
59
+ ][0]?.getDeclaration()?.typeParameters;
60
+ const parameters = directParameters !== undefined ? directParameters : signatureParameters;
61
+ const defaultNode = parameters?.[parameterIndex]?.default;
62
+ if (!defaultNode) {
63
+ throw new Error(`Missing default for type parameter ${parameterIndex} of export "${selector.exportName}" in "${selector.fileName}"`);
64
+ }
65
+ if (typescript.isTypeReferenceNode(defaultNode) &&
66
+ typescript.isIdentifier(defaultNode.typeName)) {
67
+ const referencedName = defaultNode.typeName.text;
68
+ const previousIndex = parameters?.findIndex((parameter, index) => index < parameterIndex && parameter.name.text === referencedName);
69
+ if (previousIndex !== undefined && previousIndex >= 0 && parameters?.[previousIndex]?.default) {
70
+ return exportedDefaultType(typescript, program, checker, selector, previousIndex);
71
+ }
72
+ }
73
+ const previousSymbols = new Set(parameters
74
+ ?.slice(0, parameterIndex)
75
+ .map((parameter) => checker.getSymbolAtLocation(parameter.name))
76
+ .filter((symbol) => symbol !== undefined));
77
+ let hasDependentReference = false;
78
+ function visit(node) {
79
+ const symbol = typescript.isIdentifier(node) ? checker.getSymbolAtLocation(node) : undefined;
80
+ if (symbol && previousSymbols.has(symbol)) {
81
+ hasDependentReference = true;
82
+ return;
83
+ }
84
+ typescript.forEachChild(node, visit);
85
+ }
86
+ visit(defaultNode);
87
+ if (hasDependentReference) {
88
+ throw new Error(`Unsupported dependent composite default for type parameter ${parameterIndex} of export "${selector.exportName}" in "${selector.fileName}"; export and query a named instantiated type instead`);
89
+ }
90
+ return checker.getTypeAtLocation(defaultNode);
91
+ }
@@ -0,0 +1,22 @@
1
+ import type ts from './typescript-api.mts';
2
+ import { type ExportedTypeSelector } from './type-query-export.mts';
3
+ import { type TypeScriptApi, type TypeScriptProgram } from './type-query-api.mts';
4
+ export type { ExportedTypeSelector } from './type-query-export.mts';
5
+ export type { TypeScriptApi, TypeScriptProgram } from './type-query-api.mts';
6
+ export interface TypeFactsRequest {
7
+ readonly propertyNames?: readonly string[];
8
+ readonly assignableTo?: Readonly<Record<string, ExportedTypeSelector>>;
9
+ }
10
+ export interface TypeFacts {
11
+ readonly display: string;
12
+ readonly isAny: boolean;
13
+ readonly properties: Readonly<Record<string, string | undefined>>;
14
+ readonly assignableTo: Readonly<Record<string, boolean>>;
15
+ }
16
+ export interface ExportedTypeFactsRequest extends ExportedTypeSelector, TypeFactsRequest {
17
+ readonly program: TypeScriptProgram;
18
+ readonly typescript: TypeScriptApi;
19
+ readonly defaultTypeParameterIndex?: number;
20
+ }
21
+ export declare function factsForType(typescript: TypeScriptApi, program: ts.Program, checker: ts.TypeChecker, type: ts.Type, at: ts.Node, request: TypeFactsRequest): TypeFacts;
22
+ export declare function getExportedTypeFacts(request: ExportedTypeFactsRequest): TypeFacts;
@@ -0,0 +1,39 @@
1
+ import { exportedAssignableType, exportedDefaultType, exportedType, sourceFileForQuery, } from './type-query-export.mjs';
2
+ import { programForQuery, validateTypeScriptApi, } from './type-query-api.mjs';
3
+ export function factsForType(typescript, program, checker, type, at, request) {
4
+ const properties = Object.fromEntries([...new Set(request.propertyNames ?? [])].sort().map((name) => {
5
+ const property = checker.getPropertyOfType(type, name);
6
+ const numericName = Number(name);
7
+ const numericKey = name.trim() !== '' && String(numericName) === name;
8
+ const propertyType = property
9
+ ? checker.getTypeOfSymbolAtLocation(property, at)
10
+ : (checker.getIndexTypeOfType(type, numericKey ? typescript.IndexKind.Number : typescript.IndexKind.String) ??
11
+ (numericKey ? checker.getIndexTypeOfType(type, typescript.IndexKind.String) : undefined));
12
+ const display = propertyType
13
+ ? checker.typeToString(propertyType, undefined, typescript.TypeFormatFlags.NoTruncation)
14
+ : undefined;
15
+ return [name, display];
16
+ }));
17
+ const assignableTo = Object.fromEntries(Object.entries(request.assignableTo ?? {})
18
+ .sort(([a], [b]) => a.localeCompare(b))
19
+ .map(([label, target]) => [
20
+ label,
21
+ checker.isTypeAssignableTo(type, exportedAssignableType(typescript, program, checker, target)),
22
+ ]));
23
+ return {
24
+ display: checker.typeToString(type, undefined, typescript.TypeFormatFlags.NoTruncation),
25
+ isAny: Boolean(type.flags & typescript.TypeFlags.Any),
26
+ properties,
27
+ assignableTo,
28
+ };
29
+ }
30
+ export function getExportedTypeFacts(request) {
31
+ const program = programForQuery(request.program);
32
+ const checker = program.getTypeChecker();
33
+ const sourceFile = sourceFileForQuery(program, request.fileName);
34
+ validateTypeScriptApi(request.typescript, request.program, sourceFile);
35
+ const type = request.defaultTypeParameterIndex === undefined
36
+ ? exportedType(request.typescript, program, checker, request)
37
+ : exportedDefaultType(request.typescript, program, checker, request, request.defaultTypeParameterIndex);
38
+ return factsForType(request.typescript, program, checker, type, sourceFile, request);
39
+ }
@@ -0,0 +1,9 @@
1
+ import ts from '@typescript/typescript6';
2
+ export declare function collectCallExpressions(content: string, file: string): ts.CallExpression[];
3
+ export declare function findConstObjectLiteral(content: string, name: string, file: string): ts.ObjectLiteralExpression;
4
+ export declare function hasConfiguredObjectDeclaration(content: string, name: string, file: string): boolean;
5
+ export declare function findTypeAliasDeclaration(content: string, name: string, file: string): ts.TypeAliasDeclaration;
6
+ export declare function getCallExpressionName(expression: ts.Expression): string | undefined;
7
+ export declare function getPropertyNameText(name: ts.PropertyName): string | undefined;
8
+ export declare function getStringLiteralValue(node: ts.Node | undefined): string | undefined;
9
+ export declare function unwrapExpression<T extends ts.Node>(node: T): T;
@@ -0,0 +1,94 @@
1
+ import ts from '@typescript/typescript6';
2
+ export function collectCallExpressions(content, file) {
3
+ const sourceFile = createSourceFile(content, file);
4
+ const calls = [];
5
+ walkAst(sourceFile, (node) => {
6
+ if (ts.isCallExpression(node))
7
+ calls.push(node);
8
+ });
9
+ return calls;
10
+ }
11
+ export function findConstObjectLiteral(content, name, file) {
12
+ const sourceFile = createSourceFile(content, file);
13
+ const declaration = findConstVariableDeclaration(sourceFile, name);
14
+ if (!declaration)
15
+ throw new Error(`${file}: could not find ${name}`);
16
+ const initializer = declaration.initializer && unwrapExpression(declaration.initializer);
17
+ if (!initializer || !ts.isObjectLiteralExpression(initializer))
18
+ throw new Error(`${file}: could not find ${name}`);
19
+ if (!initializer.getText(sourceFile).trimEnd().endsWith('}'))
20
+ throw new Error(`${file}: could not find end of ${name}`);
21
+ return initializer;
22
+ }
23
+ export function hasConfiguredObjectDeclaration(content, name, file) {
24
+ const sourceFile = createSourceFile(content, file);
25
+ return sourceFile.statements.some((statement) => ts.isVariableStatement(statement) &&
26
+ statement.declarationList.declarations.some((declaration) => ts.isIdentifier(declaration.name) && declaration.name.text === name));
27
+ }
28
+ export function findTypeAliasDeclaration(content, name, file) {
29
+ const sourceFile = createSourceFile(content, file);
30
+ for (const statement of sourceFile.statements) {
31
+ if (ts.isTypeAliasDeclaration(statement) && statement.name.text === name)
32
+ return statement;
33
+ }
34
+ throw new Error(`${file}: could not parse ${name} union`);
35
+ }
36
+ export function getCallExpressionName(expression) {
37
+ const node = unwrapExpression(expression);
38
+ if (ts.isIdentifier(node))
39
+ return node.text;
40
+ if (ts.isPropertyAccessExpression(node))
41
+ return node.name.text;
42
+ if (ts.isElementAccessExpression(node))
43
+ return getStringLiteralValue(node.argumentExpression);
44
+ return undefined;
45
+ }
46
+ export function getPropertyNameText(name) {
47
+ if (ts.isIdentifier(name))
48
+ return name.text;
49
+ if (ts.isStringLiteral(name))
50
+ return name.text;
51
+ if (ts.isNumericLiteral(name))
52
+ return name.text;
53
+ return undefined;
54
+ }
55
+ export function getStringLiteralValue(node) {
56
+ if (!node)
57
+ return undefined;
58
+ const value = unwrapExpression(node);
59
+ if (ts.isStringLiteral(value) || ts.isNoSubstitutionTemplateLiteral(value))
60
+ return value.text;
61
+ return undefined;
62
+ }
63
+ export function unwrapExpression(node) {
64
+ let current = node;
65
+ while (ts.isParenthesizedExpression(current) ||
66
+ ts.isAsExpression(current) ||
67
+ ts.isTypeAssertionExpression(current) ||
68
+ ts.isSatisfiesExpression(current) ||
69
+ ts.isNonNullExpression(current)) {
70
+ current = current.expression;
71
+ }
72
+ return current;
73
+ }
74
+ function walkAst(node, onNode) {
75
+ onNode(node);
76
+ ts.forEachChild(node, (child) => walkAst(child, onNode));
77
+ }
78
+ function createSourceFile(content, file) {
79
+ const scriptKind = file.endsWith('.tsx') || file.endsWith('.jsx') ? ts.ScriptKind.TSX : ts.ScriptKind.TS;
80
+ return ts.createSourceFile(file, content, ts.ScriptTarget.Latest, true, scriptKind);
81
+ }
82
+ function findConstVariableDeclaration(sourceFile, name) {
83
+ for (const statement of sourceFile.statements) {
84
+ if (!ts.isVariableStatement(statement))
85
+ continue;
86
+ if ((statement.declarationList.flags & ts.NodeFlags.Const) === 0)
87
+ continue;
88
+ for (const declaration of statement.declarationList.declarations) {
89
+ if (ts.isIdentifier(declaration.name) && declaration.name.text === name)
90
+ return declaration;
91
+ }
92
+ }
93
+ return undefined;
94
+ }
@@ -0,0 +1,3 @@
1
+ import type { SharedContext } from '../shared-context/index.mts';
2
+ import type { FiniteEnumRippleConfig } from './model.mts';
3
+ export declare function checkFiniteEnumRipple(ctx: SharedContext, config: FiniteEnumRippleConfig): string[];
@@ -0,0 +1,62 @@
1
+ import { readFileSync } from 'node:fs';
2
+ import { join } from 'node:path';
3
+ import { checkStructuredTypes } from './types.mjs';
4
+ import { checkUnionTypes } from './union-types.mjs';
5
+ import { escapeWorkflowData, finiteEnumError } from './compare.mjs';
6
+ export function checkFiniteEnumRipple(ctx, config) {
7
+ const errors = [];
8
+ const readTracked = (file) => {
9
+ if (!config.files.existingFileSet.has(file))
10
+ throw new Error(`Not tracked: ${file}`);
11
+ const content = ctx.readTrackedFile?.(file);
12
+ if (content !== undefined) {
13
+ if (content === null)
14
+ throw new Error(`Cannot read tracked file: ${file}`);
15
+ return content;
16
+ }
17
+ try {
18
+ return readFileSync(join(ctx.repoRoot, file), 'utf8');
19
+ }
20
+ catch (cause) {
21
+ throw new Error(`${file}: ${String(cause)}`);
22
+ }
23
+ };
24
+ if (config.structured) {
25
+ try {
26
+ checkStructuredTypes(errors, config.files, readTracked, config.structured);
27
+ }
28
+ catch (cause) {
29
+ errors.push(familyError(cause, config.structured.backendPath, config.files, [
30
+ config.structured.webPath,
31
+ config.structured.routeConfigsPath,
32
+ ]));
33
+ }
34
+ }
35
+ if (config.union) {
36
+ try {
37
+ checkUnionTypes(errors, config.files, readTracked, config.union);
38
+ }
39
+ catch (cause) {
40
+ errors.push(familyError(cause, config.union.typesPath, config.files, [config.union.routeConfigsPath]));
41
+ }
42
+ }
43
+ return errors.map((error) => `${error}${escapeWorkflowData(config.diagnosticSuffix ?? '')}`);
44
+ }
45
+ function familyError(cause, defaultFile, files, otherPaths) {
46
+ const message = String(cause instanceof Error ? cause.message : cause);
47
+ const selectedPaths = [
48
+ defaultFile,
49
+ ...otherPaths,
50
+ ...files.structuredDetailPages.map((page) => page.file),
51
+ ...files.structuredCollectionPages.map((page) => page.file),
52
+ ...files.structuredComponentFiles.map((page) => page.file),
53
+ ...files.unionDetailPages.map((page) => page.file),
54
+ ...files.unionCollectionPages.map((page) => page.file),
55
+ ...files.unionCreatePages.map((page) => page.file),
56
+ ];
57
+ const file = [...selectedPaths]
58
+ .sort((a, b) => b.length - a.length)
59
+ .find((path) => message.startsWith(`${path}:`) || message.endsWith(`: ${path}`)) ??
60
+ defaultFile;
61
+ return finiteEnumError(file, message.startsWith(`${file}: `) ? message.slice(file.length + 2) : message);
62
+ }
@@ -0,0 +1,20 @@
1
+ import type { FiniteEnumFiles, ReadTrackedFile, RoutedPage } from './model.mts';
2
+ export declare function hasAllFiles(files: FiniteEnumFiles, paths: readonly string[]): boolean;
3
+ export declare function checkUnionCreatePageTypes(errors: string[], routeConfigs: {
4
+ pluralPath: string;
5
+ unionTypes: string[];
6
+ }[], pages: readonly RoutedPage[], readTracked: ReadTrackedFile, createPageTypeProperties: readonly string[], label: string): void;
7
+ export declare function checkCollectionPagePathLiterals(pages: readonly RoutedPage[], errors: string[], group: string, routeSlugs: ReadonlySet<string>, readTracked: ReadTrackedFile, pathPattern: RegExp): void;
8
+ export declare function compareSets(errors: string[], options: {
9
+ label: string;
10
+ actualLabel: string;
11
+ actualFile: string;
12
+ actualValues: readonly string[];
13
+ expectedLabel: string;
14
+ expectedFile: string;
15
+ expectedValues: readonly string[];
16
+ }): void;
17
+ export declare function uniqueSorted(values: readonly string[]): string[];
18
+ export declare function routePageSlugs(pages: readonly RoutedPage[], topOnly?: boolean): string[];
19
+ export declare function finiteEnumError(file: string, message: string): string;
20
+ export declare function escapeWorkflowData(value: string): string;
@@ -0,0 +1,90 @@
1
+ import { collectCreatePageLiterals } from './create-page-literals.mjs';
2
+ import { collectActivePatternCaptures } from './pattern-captures.mjs';
3
+ export function hasAllFiles(files, paths) {
4
+ return paths.every((file) => files.existingFileSet.has(file));
5
+ }
6
+ export function checkUnionCreatePageTypes(errors, routeConfigs, pages, readTracked, createPageTypeProperties, label) {
7
+ const typeByPluralPath = new Map(routeConfigs.flatMap((config) => config.unionTypes.length === 1 ? [[config.pluralPath, config.unionTypes[0]]] : []));
8
+ for (const page of pages) {
9
+ const expectedType = typeByPluralPath.get(page.slug);
10
+ if (expectedType === undefined) {
11
+ errors.push(finiteEnumError(page.file, `${label} create page has no matching single-type route config for "${page.slug}"`));
12
+ continue;
13
+ }
14
+ const content = readTracked(page.file);
15
+ const actualTypes = collectCreatePageLiterals(content, page.file, createPageTypeProperties);
16
+ if (actualTypes.length === 0)
17
+ errors.push(finiteEnumError(page.file, `${label} create page has no inspectable type literal`));
18
+ for (const actualType of actualTypes) {
19
+ if (actualType === expectedType)
20
+ continue;
21
+ errors.push(finiteEnumError(page.file, `${label} create page literal uses "${actualType}" but ${page.slug} expects "${expectedType}"`));
22
+ }
23
+ }
24
+ }
25
+ export function checkCollectionPagePathLiterals(pages, errors, group, routeSlugs, readTracked, pathPattern) {
26
+ for (const page of pages) {
27
+ if (!routeSlugs.has(page.slug))
28
+ continue;
29
+ const content = readTracked(page.file);
30
+ for (const literalPath of collectActivePatternCaptures(content, pathPattern, page.file)) {
31
+ if (literalPath === page.slug || literalPath.startsWith(`${page.slug}/`))
32
+ continue;
33
+ errors.push(finiteEnumError(page.file, `${group} collection path literal "/${literalPath}" does not match route directory "${page.slug}"`));
34
+ }
35
+ }
36
+ }
37
+ export function compareSets(errors, options) {
38
+ for (const duplicate of duplicateValues(options.actualValues)) {
39
+ errors.push(finiteEnumError(options.actualFile, `${options.label} duplicate in actual values: ${duplicate}`));
40
+ }
41
+ for (const duplicate of duplicateValues(options.expectedValues)) {
42
+ errors.push(finiteEnumError(options.expectedFile, `${options.label} duplicate in expected values: ${duplicate}`));
43
+ }
44
+ const actual = uniqueSorted(options.actualValues);
45
+ const expected = uniqueSorted(options.expectedValues);
46
+ const missing = expected.filter((value) => !actual.includes(value));
47
+ const extra = actual.filter((value) => !expected.includes(value));
48
+ if (missing.length === 0 && extra.length === 0)
49
+ return;
50
+ const parts = [
51
+ `${options.label} mismatch`,
52
+ missing.length > 0 ? `missing from ${options.actualLabel}: ${missing.join(', ')}` : '',
53
+ extra.length > 0 ? `stale in ${options.actualLabel}: ${extra.join(', ')}` : '',
54
+ `expected from ${options.expectedLabel}: ${expected.join(', ')}`,
55
+ ].filter(Boolean);
56
+ errors.push(finiteEnumError(options.actualFile, parts.join('; ')));
57
+ }
58
+ export function uniqueSorted(values) {
59
+ return [...new Set(values)].toSorted();
60
+ }
61
+ export function routePageSlugs(pages, topOnly = false) {
62
+ const slugs = new Set();
63
+ for (const page of pages) {
64
+ if (!topOnly || page.isTopLevel)
65
+ slugs.add(page.slug);
66
+ }
67
+ return [...slugs].toSorted();
68
+ }
69
+ function duplicateValues(values) {
70
+ const seen = new Set();
71
+ const duplicates = new Set();
72
+ for (const value of values) {
73
+ if (seen.has(value))
74
+ duplicates.add(value);
75
+ seen.add(value);
76
+ }
77
+ return [...duplicates].toSorted();
78
+ }
79
+ export function finiteEnumError(file, message) {
80
+ const escapeProperty = (value) => value
81
+ .replaceAll('%', '%25')
82
+ .replaceAll('\r', '%0D')
83
+ .replaceAll('\n', '%0A')
84
+ .replaceAll(':', '%3A')
85
+ .replaceAll(',', '%2C');
86
+ return `::error file=${escapeProperty(file)}::${escapeWorkflowData(`${file}: ${message}.`)}`;
87
+ }
88
+ export function escapeWorkflowData(value) {
89
+ return value.replaceAll('%', '%25').replaceAll('\r', '%0D').replaceAll('\n', '%0A');
90
+ }
@@ -0,0 +1,8 @@
1
+ import type { ReadTrackedFile } from './model.mts';
2
+ export declare function checkStructuredCollectionComponentPathLiterals(errors: string[], routeConfigs: {
3
+ pluralPath: string;
4
+ singularPath: string;
5
+ }[], componentFiles: readonly {
6
+ file: string;
7
+ slug: string;
8
+ }[], ignoredNavigationPaths: ReadonlySet<string>, readTracked: ReadTrackedFile, label: string, navigationPattern: RegExp): void;
@@ -0,0 +1,30 @@
1
+ import { collectActivePatternCaptures } from './pattern-captures.mjs';
2
+ import { finiteEnumError } from './compare.mjs';
3
+ export function checkStructuredCollectionComponentPathLiterals(errors, routeConfigs, componentFiles, ignoredNavigationPaths, readTracked, label, navigationPattern) {
4
+ const configByPluralPath = new Map(routeConfigs.map((config) => [config.pluralPath, config]));
5
+ for (const { file, slug } of componentFiles) {
6
+ const config = configByPluralPath.get(slug);
7
+ if (!config) {
8
+ errors.push(finiteEnumError(file, `${label} component has no matching route config for "${slug}"`));
9
+ continue;
10
+ }
11
+ const content = readTracked(file);
12
+ for (const literalPath of collectNavigationPathLiterals(content, file, ignoredNavigationPaths, navigationPattern)) {
13
+ if (matchesConfiguredPath(literalPath, config.pluralPath) ||
14
+ matchesConfiguredPath(literalPath, config.singularPath))
15
+ continue;
16
+ errors.push(finiteEnumError(file, `${label} component navigation path "/${literalPath}" does not match route config "${config.singularPath}" or "${config.pluralPath}"`));
17
+ }
18
+ }
19
+ }
20
+ function matchesConfiguredPath(literalPath, configuredPath) {
21
+ return literalPath === configuredPath || literalPath.startsWith(`${configuredPath}/`);
22
+ }
23
+ function collectNavigationPathLiterals(content, file, ignoredPaths, pattern) {
24
+ const paths = [];
25
+ for (const literalPath of collectActivePatternCaptures(content, pattern, file)) {
26
+ if (!ignoredPaths.has(literalPath))
27
+ paths.push(literalPath);
28
+ }
29
+ return paths;
30
+ }
@@ -0,0 +1 @@
1
+ export declare function hasPostDeclarationConfiguredObjectMutation(content: string, file: string, name: string, objectEnd: number): boolean;
@@ -0,0 +1,78 @@
1
+ import ts from '@typescript/typescript6';
2
+ import { resolve } from 'node:path';
3
+ import { unwrapExpression } from './ast.mjs';
4
+ export function hasPostDeclarationConfiguredObjectMutation(content, file, name, objectEnd) {
5
+ const fileName = resolve(file);
6
+ const scriptKind = file.endsWith('.tsx') || file.endsWith('.jsx') ? ts.ScriptKind.TSX : ts.ScriptKind.TS;
7
+ const source = ts.createSourceFile(fileName, content, ts.ScriptTarget.Latest, true, scriptKind);
8
+ const options = {
9
+ noLib: true,
10
+ noResolve: true,
11
+ target: ts.ScriptTarget.Latest,
12
+ };
13
+ const host = ts.createCompilerHost(options);
14
+ host.getSourceFile = () => source;
15
+ const program = ts.createProgram([fileName], options, host);
16
+ const checker = program.getTypeChecker();
17
+ const findDeclaration = (node) => {
18
+ if (ts.isVariableDeclaration(node) &&
19
+ ts.isIdentifier(node.name) &&
20
+ node.name.text === name &&
21
+ node.initializer &&
22
+ ts.isObjectLiteralExpression(unwrapExpression(node.initializer)) &&
23
+ unwrapExpression(node.initializer).end === objectEnd)
24
+ return node.name;
25
+ return ts.forEachChild(node, findDeclaration);
26
+ };
27
+ // The public parser has already verified this exact const object declaration.
28
+ const configuredName = findDeclaration(source);
29
+ const configuredSymbol = checker.getSymbolAtLocation(configuredName);
30
+ const targetsConfiguredMap = (target) => {
31
+ let root = unwrapExpression(target);
32
+ while (ts.isPropertyAccessExpression(root) || ts.isElementAccessExpression(root))
33
+ root = unwrapExpression(root.expression);
34
+ return ts.isIdentifier(root) && checker.getSymbolAtLocation(root) === configuredSymbol;
35
+ };
36
+ const destructuringTargetMutatesConfiguredMap = (target) => {
37
+ const expression = unwrapExpression(target);
38
+ if (targetsConfiguredMap(expression))
39
+ return true;
40
+ if (ts.isObjectLiteralExpression(expression))
41
+ return expression.properties.some((property) => ts.isPropertyAssignment(property)
42
+ ? destructuringTargetMutatesConfiguredMap(property.initializer)
43
+ : ts.isShorthandPropertyAssignment(property)
44
+ ? checker.getSymbolAtLocation(property.name) === configuredSymbol
45
+ : ts.isSpreadAssignment(property) &&
46
+ destructuringTargetMutatesConfiguredMap(property.expression));
47
+ if (ts.isArrayLiteralExpression(expression))
48
+ return expression.elements.some((element) => !ts.isOmittedExpression(element) && destructuringTargetMutatesConfiguredMap(element));
49
+ return false;
50
+ };
51
+ let mutated = false;
52
+ const inspect = (node) => {
53
+ if (mutated || node.end <= objectEnd)
54
+ return;
55
+ if (ts.isBinaryExpression(node) &&
56
+ (node.operatorToken.kind === ts.SyntaxKind.EqualsToken ||
57
+ (node.operatorToken.kind >= ts.SyntaxKind.FirstCompoundAssignment &&
58
+ node.operatorToken.kind <= ts.SyntaxKind.LastCompoundAssignment)) &&
59
+ destructuringTargetMutatesConfiguredMap(node.left)) {
60
+ mutated = true;
61
+ return;
62
+ }
63
+ if (ts.isDeleteExpression(node) && targetsConfiguredMap(node.expression)) {
64
+ mutated = true;
65
+ return;
66
+ }
67
+ if ((ts.isPrefixUnaryExpression(node) || ts.isPostfixUnaryExpression(node)) &&
68
+ (node.operator === ts.SyntaxKind.PlusPlusToken ||
69
+ node.operator === ts.SyntaxKind.MinusMinusToken) &&
70
+ targetsConfiguredMap(node.operand)) {
71
+ mutated = true;
72
+ return;
73
+ }
74
+ ts.forEachChild(node, inspect);
75
+ };
76
+ inspect(source);
77
+ return mutated;
78
+ }