@servicenow/sdk-build-core 3.0.3 → 4.0.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.
- package/dist/app.d.ts +25 -0
- package/dist/app.js +8 -0
- package/dist/app.js.map +1 -0
- package/dist/compiler.d.ts +60 -0
- package/dist/compiler.js +320 -0
- package/dist/compiler.js.map +1 -0
- package/dist/compression.d.ts +7 -0
- package/dist/compression.js +79 -0
- package/dist/compression.js.map +1 -0
- package/dist/crypto.d.ts +1 -0
- package/dist/crypto.js +9 -0
- package/dist/crypto.js.map +1 -0
- package/dist/diagnostic.d.ts +41 -0
- package/dist/diagnostic.js +130 -0
- package/dist/diagnostic.js.map +1 -0
- package/dist/{plugins/Diagnostic.d.ts → fluent-diagnostic.d.ts} +3 -2
- package/dist/fluent-diagnostic.js +23 -0
- package/dist/fluent-diagnostic.js.map +1 -0
- package/dist/fluent-directive.d.ts +8 -0
- package/dist/fluent-directive.js +54 -0
- package/dist/fluent-directive.js.map +1 -0
- package/dist/fluent-file.d.ts +5 -0
- package/dist/fluent-file.js +15 -0
- package/dist/fluent-file.js.map +1 -0
- package/dist/formatter.d.ts +11 -0
- package/dist/formatter.js +77 -0
- package/dist/formatter.js.map +1 -0
- package/dist/fs.d.ts +174 -0
- package/dist/fs.js +313 -0
- package/dist/fs.js.map +1 -0
- package/dist/guid.d.ts +2 -0
- package/dist/{GUID.js → guid.js} +3 -6
- package/dist/guid.js.map +1 -0
- package/dist/index.d.ts +19 -5
- package/dist/index.js +19 -5
- package/dist/index.js.map +1 -1
- package/dist/json.d.ts +5 -0
- package/dist/json.js +43 -0
- package/dist/json.js.map +1 -0
- package/dist/keys-registry.d.ts +64 -0
- package/dist/keys-registry.js +339 -0
- package/dist/keys-registry.js.map +1 -0
- package/dist/logger.d.ts +8 -0
- package/dist/logger.js +17 -0
- package/dist/logger.js.map +1 -0
- package/dist/now-config.d.ts +348 -0
- package/dist/now-config.js +283 -0
- package/dist/now-config.js.map +1 -0
- package/dist/path.d.ts +3 -0
- package/dist/path.js +12 -0
- package/dist/path.js.map +1 -0
- package/dist/plugins/cache.d.ts +20 -0
- package/dist/plugins/cache.js +46 -0
- package/dist/plugins/cache.js.map +1 -0
- package/dist/plugins/context.d.ts +85 -0
- package/dist/plugins/{Context.js → context.js} +1 -1
- package/dist/plugins/context.js.map +1 -0
- package/dist/plugins/database.d.ts +27 -0
- package/dist/plugins/database.js +102 -0
- package/dist/plugins/database.js.map +1 -0
- package/dist/plugins/file.d.ts +10 -0
- package/dist/plugins/{behaviors/Arranger.js → file.js} +1 -1
- package/dist/plugins/file.js.map +1 -0
- package/dist/plugins/index.d.ts +9 -5
- package/dist/plugins/index.js +9 -6
- package/dist/plugins/index.js.map +1 -1
- package/dist/plugins/plugin.d.ts +478 -0
- package/dist/plugins/plugin.js +533 -0
- package/dist/plugins/plugin.js.map +1 -0
- package/dist/plugins/product.d.ts +15 -0
- package/dist/plugins/product.js +38 -0
- package/dist/plugins/product.js.map +1 -0
- package/dist/plugins/project.d.ts +25 -0
- package/dist/plugins/{behaviors/Generator.js → project.js} +1 -1
- package/dist/plugins/project.js.map +1 -0
- package/dist/plugins/shape.d.ts +424 -0
- package/dist/plugins/shape.js +1181 -0
- package/dist/plugins/shape.js.map +1 -0
- package/dist/plugins/time.d.ts +12 -0
- package/dist/plugins/time.js +84 -0
- package/dist/plugins/time.js.map +1 -0
- package/dist/plugins/usage.d.ts +11 -0
- package/dist/plugins/usage.js +26 -0
- package/dist/plugins/usage.js.map +1 -0
- package/dist/prettier/config-loader.d.ts +13 -0
- package/dist/prettier/config-loader.js +105 -0
- package/dist/prettier/config-loader.js.map +1 -0
- package/dist/telemetry/index.d.ts +25 -0
- package/dist/telemetry/index.js +18 -0
- package/dist/telemetry/index.js.map +1 -0
- package/dist/typescript.d.ts +293 -0
- package/dist/typescript.js +454 -0
- package/dist/typescript.js.map +1 -0
- package/dist/util/get-file-type.d.ts +2 -0
- package/dist/util/get-file-type.js +13 -0
- package/dist/util/get-file-type.js.map +1 -0
- package/dist/util/index.d.ts +2 -6
- package/dist/util/index.js +2 -6
- package/dist/util/index.js.map +1 -1
- package/dist/util/{Scope.js → is-sn-scope.js} +1 -1
- package/dist/util/is-sn-scope.js.map +1 -0
- package/dist/xml.d.ts +24 -0
- package/dist/xml.js +71 -0
- package/dist/xml.js.map +1 -0
- package/now.config.schema.json +336 -0
- package/package.json +22 -12
- package/src/app.ts +33 -0
- package/src/compiler.ts +384 -0
- package/src/compression.ts +93 -0
- package/src/crypto.ts +5 -0
- package/src/diagnostic.ts +108 -0
- package/src/{plugins/Diagnostic.ts → fluent-diagnostic.ts} +3 -10
- package/src/fluent-directive.ts +63 -0
- package/src/fluent-file.ts +13 -0
- package/src/formatter.ts +58 -0
- package/src/fs.ts +438 -0
- package/src/{GUID.ts → guid.ts} +2 -6
- package/src/index.ts +19 -5
- package/src/json.ts +20 -0
- package/src/keys-registry.ts +384 -0
- package/src/logger.ts +20 -0
- package/src/now-config.ts +337 -0
- package/src/path.ts +9 -0
- package/src/plugins/cache.ts +45 -0
- package/src/plugins/context.ts +93 -0
- package/src/plugins/database.ts +121 -0
- package/src/plugins/file.ts +19 -0
- package/src/plugins/index.ts +9 -5
- package/src/plugins/plugin.ts +995 -0
- package/src/plugins/product.ts +44 -0
- package/src/plugins/project.ts +39 -0
- package/src/plugins/shape.ts +1532 -0
- package/src/plugins/time.ts +108 -0
- package/src/plugins/usage.ts +26 -0
- package/src/prettier/config-loader.ts +130 -0
- package/src/telemetry/index.ts +27 -0
- package/src/typescript.ts +502 -0
- package/src/util/get-file-type.ts +11 -0
- package/src/util/index.ts +2 -6
- package/src/xml.ts +86 -0
- package/dist/GUID.d.ts +0 -2
- package/dist/GUID.js.map +0 -1
- package/dist/IncludePaths.d.ts +0 -25
- package/dist/IncludePaths.js +0 -97
- package/dist/IncludePaths.js.map +0 -1
- package/dist/Keys.d.ts +0 -32
- package/dist/Keys.js +0 -245
- package/dist/Keys.js.map +0 -1
- package/dist/TypeScript.d.ts +0 -5
- package/dist/TypeScript.js +0 -58
- package/dist/TypeScript.js.map +0 -1
- package/dist/XML.d.ts +0 -32
- package/dist/XML.js +0 -83
- package/dist/XML.js.map +0 -1
- package/dist/plugins/Context.d.ts +0 -190
- package/dist/plugins/Context.js.map +0 -1
- package/dist/plugins/Diagnostic.js +0 -28
- package/dist/plugins/Diagnostic.js.map +0 -1
- package/dist/plugins/Plugin.d.ts +0 -175
- package/dist/plugins/Plugin.js +0 -15
- package/dist/plugins/Plugin.js.map +0 -1
- package/dist/plugins/behaviors/Arranger.d.ts +0 -26
- package/dist/plugins/behaviors/Arranger.js.map +0 -1
- package/dist/plugins/behaviors/Composer.d.ts +0 -102
- package/dist/plugins/behaviors/Composer.js +0 -15
- package/dist/plugins/behaviors/Composer.js.map +0 -1
- package/dist/plugins/behaviors/Diagnostics.d.ts +0 -7
- package/dist/plugins/behaviors/Diagnostics.js +0 -3
- package/dist/plugins/behaviors/Diagnostics.js.map +0 -1
- package/dist/plugins/behaviors/Generator.d.ts +0 -21
- package/dist/plugins/behaviors/Generator.js.map +0 -1
- package/dist/plugins/behaviors/OwnedTables.d.ts +0 -6
- package/dist/plugins/behaviors/OwnedTables.js +0 -3
- package/dist/plugins/behaviors/OwnedTables.js.map +0 -1
- package/dist/plugins/behaviors/PostProcessor.d.ts +0 -5
- package/dist/plugins/behaviors/PostProcessor.js +0 -3
- package/dist/plugins/behaviors/PostProcessor.js.map +0 -1
- package/dist/plugins/behaviors/Serializer.d.ts +0 -30
- package/dist/plugins/behaviors/Serializer.js +0 -3
- package/dist/plugins/behaviors/Serializer.js.map +0 -1
- package/dist/plugins/behaviors/Transformer.d.ts +0 -23
- package/dist/plugins/behaviors/Transformer.js +0 -3
- package/dist/plugins/behaviors/Transformer.js.map +0 -1
- package/dist/plugins/behaviors/extractors/Data.d.ts +0 -119
- package/dist/plugins/behaviors/extractors/Data.js +0 -244
- package/dist/plugins/behaviors/extractors/Data.js.map +0 -1
- package/dist/plugins/behaviors/extractors/Extractors.d.ts +0 -63
- package/dist/plugins/behaviors/extractors/Extractors.js +0 -3
- package/dist/plugins/behaviors/extractors/Extractors.js.map +0 -1
- package/dist/plugins/behaviors/extractors/index.d.ts +0 -2
- package/dist/plugins/behaviors/extractors/index.js +0 -19
- package/dist/plugins/behaviors/extractors/index.js.map +0 -1
- package/dist/plugins/behaviors/index.d.ts +0 -9
- package/dist/plugins/behaviors/index.js +0 -26
- package/dist/plugins/behaviors/index.js.map +0 -1
- package/dist/plugins/util/CallExpression.d.ts +0 -5
- package/dist/plugins/util/CallExpression.js +0 -88
- package/dist/plugins/util/CallExpression.js.map +0 -1
- package/dist/plugins/util/CodeTransformation.d.ts +0 -95
- package/dist/plugins/util/CodeTransformation.js +0 -624
- package/dist/plugins/util/CodeTransformation.js.map +0 -1
- package/dist/plugins/util/ObjectLiteral.d.ts +0 -9
- package/dist/plugins/util/ObjectLiteral.js +0 -37
- package/dist/plugins/util/ObjectLiteral.js.map +0 -1
- package/dist/plugins/util/index.d.ts +0 -3
- package/dist/plugins/util/index.js +0 -20
- package/dist/plugins/util/index.js.map +0 -1
- package/dist/util/Debug.d.ts +0 -4
- package/dist/util/Debug.js +0 -20
- package/dist/util/Debug.js.map +0 -1
- package/dist/util/Directive.d.ts +0 -16
- package/dist/util/Directive.js +0 -107
- package/dist/util/Directive.js.map +0 -1
- package/dist/util/RuntimeTableSchema.d.ts +0 -5
- package/dist/util/RuntimeTableSchema.js +0 -58
- package/dist/util/RuntimeTableSchema.js.map +0 -1
- package/dist/util/Scope.js.map +0 -1
- package/dist/util/Util.d.ts +0 -1
- package/dist/util/Util.js +0 -12
- package/dist/util/Util.js.map +0 -1
- package/dist/util/XMLUploadParser.d.ts +0 -22
- package/dist/util/XMLUploadParser.js +0 -67
- package/dist/util/XMLUploadParser.js.map +0 -1
- package/src/IncludePaths.ts +0 -122
- package/src/Keys.ts +0 -274
- package/src/TypeScript.ts +0 -65
- package/src/XML.ts +0 -98
- package/src/plugins/Context.ts +0 -239
- package/src/plugins/Plugin.ts +0 -278
- package/src/plugins/behaviors/Arranger.ts +0 -42
- package/src/plugins/behaviors/Composer.ts +0 -125
- package/src/plugins/behaviors/Diagnostics.ts +0 -12
- package/src/plugins/behaviors/Generator.ts +0 -31
- package/src/plugins/behaviors/OwnedTables.ts +0 -5
- package/src/plugins/behaviors/PostProcessor.ts +0 -6
- package/src/plugins/behaviors/Serializer.ts +0 -40
- package/src/plugins/behaviors/Transformer.ts +0 -32
- package/src/plugins/behaviors/extractors/Data.ts +0 -332
- package/src/plugins/behaviors/extractors/Extractors.ts +0 -73
- package/src/plugins/behaviors/extractors/index.ts +0 -2
- package/src/plugins/behaviors/index.ts +0 -9
- package/src/plugins/util/CallExpression.ts +0 -110
- package/src/plugins/util/CodeTransformation.ts +0 -731
- package/src/plugins/util/ObjectLiteral.ts +0 -37
- package/src/plugins/util/index.ts +0 -3
- package/src/util/Debug.ts +0 -24
- package/src/util/Directive.ts +0 -123
- package/src/util/RuntimeTableSchema.ts +0 -44
- package/src/util/Util.ts +0 -7
- package/src/util/XMLUploadParser.ts +0 -90
- /package/dist/util/{Scope.d.ts → is-sn-scope.d.ts} +0 -0
- /package/src/util/{Scope.ts → is-sn-scope.ts} +0 -0
package/src/compiler.ts
ADDED
|
@@ -0,0 +1,384 @@
|
|
|
1
|
+
import { path } from './path'
|
|
2
|
+
import { isSupportedNode, type SupportedNode, ts } from './typescript'
|
|
3
|
+
import { Diagnostic } from './diagnostic'
|
|
4
|
+
import { FileSystem, TsMorphFileSystemWrapper } from './fs'
|
|
5
|
+
import { NOW_FILE_EXTENSION } from './fluent-file'
|
|
6
|
+
import type { Diagnostics } from './plugins/context'
|
|
7
|
+
|
|
8
|
+
export type CompilerOptions = ts.CompilerOptions
|
|
9
|
+
export const DEFAULT_COMPILER_OPTIONS: CompilerOptions = {
|
|
10
|
+
noEmit: true,
|
|
11
|
+
lib: ['lib.es5.d.ts'],
|
|
12
|
+
target: ts.ScriptTarget.ES2023,
|
|
13
|
+
module: ts.ModuleKind.NodeNext,
|
|
14
|
+
esModuleInterop: true,
|
|
15
|
+
isolatedModules: true,
|
|
16
|
+
allowJs: true,
|
|
17
|
+
checkJs: false,
|
|
18
|
+
strict: true,
|
|
19
|
+
alwaysStrict: true,
|
|
20
|
+
exactOptionalPropertyTypes: true,
|
|
21
|
+
noEmitOnError: true,
|
|
22
|
+
noFallthroughCasesInSwitch: true,
|
|
23
|
+
noImplicitOverride: true,
|
|
24
|
+
noImplicitReturns: true,
|
|
25
|
+
noImplicitThis: true,
|
|
26
|
+
noImplicitAny: false,
|
|
27
|
+
noUncheckedIndexedAccess: true,
|
|
28
|
+
noUnusedLocals: true,
|
|
29
|
+
noUnusedParameters: true,
|
|
30
|
+
noPropertyAccessFromIndexSignature: true,
|
|
31
|
+
forceConsistentCasingInFileNames: true,
|
|
32
|
+
resolveJsonModule: true,
|
|
33
|
+
skipLibCheck: true,
|
|
34
|
+
skipDefaultLibCheck: true,
|
|
35
|
+
allowImportingTsExtensions: true,
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const SUPPORTED_EXTENSIONS = ['.ts', '.tsx', '.js', '.jsx', '.cts', '.cjs', '.mts', '.mjs', '.json']
|
|
39
|
+
const UNSUPPORTED_EXTENSIONS = ['.test.ts', '.test.js', '.d.ts']
|
|
40
|
+
|
|
41
|
+
export class Compiler extends ts.Project {
|
|
42
|
+
private readonly rootDir: string
|
|
43
|
+
private readonly sourceFileToRelativeModuleSpecifier: Record<string, string> = {}
|
|
44
|
+
private readonly generatedTableFilePath: string
|
|
45
|
+
private moduleProject: ts.Project | undefined
|
|
46
|
+
|
|
47
|
+
constructor(
|
|
48
|
+
private readonly fs: FileSystem,
|
|
49
|
+
{
|
|
50
|
+
rootDir = '/',
|
|
51
|
+
sourceFilePaths = [],
|
|
52
|
+
compilerOptions = DEFAULT_COMPILER_OPTIONS,
|
|
53
|
+
}: {
|
|
54
|
+
rootDir?: string
|
|
55
|
+
sourceFilePaths?: string[]
|
|
56
|
+
compilerOptions?: CompilerOptions
|
|
57
|
+
} = {}
|
|
58
|
+
) {
|
|
59
|
+
super({
|
|
60
|
+
compilerOptions,
|
|
61
|
+
fileSystem: new TsMorphFileSystemWrapper(fs),
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
this.rootDir = rootDir
|
|
65
|
+
this.generatedTableFilePath = path.join(this.rootDir, '$$GENERATED$$_common_table.ts')
|
|
66
|
+
this.addGlobalTableDefinitionFile()
|
|
67
|
+
// TODO: move this somewhere better, it doesn't work when I put the file contents in packages/core/src/global
|
|
68
|
+
this.addGlobalModuleExtensions()
|
|
69
|
+
this.addFluentSourceFilesAtPaths(sourceFilePaths)
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
addGlobalTableDefinitionFile() {
|
|
73
|
+
const globalTableDefinitionContent = `
|
|
74
|
+
import '@servicenow/sdk/global'
|
|
75
|
+
import { Table } from '@servicenow/sdk/core'
|
|
76
|
+
|
|
77
|
+
declare global {
|
|
78
|
+
namespace Now {
|
|
79
|
+
namespace Internal {
|
|
80
|
+
namespace TableSchemas {
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
interface Tables {
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
`
|
|
89
|
+
this.createSourceFile(this.generatedTableFilePath, globalTableDefinitionContent, {
|
|
90
|
+
overwrite: true,
|
|
91
|
+
scriptKind: ts.ScriptKind.TS,
|
|
92
|
+
})
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
addGlobalModuleExtensions() {
|
|
96
|
+
const globalModuleDefinition = `
|
|
97
|
+
declare module '*.html' {
|
|
98
|
+
const content: string;
|
|
99
|
+
export default content;
|
|
100
|
+
}`
|
|
101
|
+
|
|
102
|
+
this.createSourceFile(path.join(this.rootDir, '$$GENERATED$$_module_extensions.ts'), globalModuleDefinition, {
|
|
103
|
+
overwrite: true,
|
|
104
|
+
scriptKind: ts.ScriptKind.TS,
|
|
105
|
+
})
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Recursively adds any source files in the specified directory, and returns the added
|
|
110
|
+
* files. Glob patterns can be supplied to control which files are added. This will also
|
|
111
|
+
* resolve the dependencies of the added files. If the directory does not exist, an empty
|
|
112
|
+
* array will be returned.
|
|
113
|
+
*/
|
|
114
|
+
addSourceFilesFromDirectory(
|
|
115
|
+
dir: string,
|
|
116
|
+
extensions: string[] = SUPPORTED_EXTENSIONS,
|
|
117
|
+
ignore_extensions: string[] = UNSUPPORTED_EXTENSIONS
|
|
118
|
+
): ts.SourceFile[] {
|
|
119
|
+
const files = [] as ts.SourceFile[]
|
|
120
|
+
|
|
121
|
+
// Using the filesystem API directly instead of using ts-morph's directory APIs is a
|
|
122
|
+
// workaround for this issue: https://github.com/dsherret/ts-morph/issues/1554
|
|
123
|
+
FileSystem.traverseDirectory(this.fs, dir, {
|
|
124
|
+
extensions,
|
|
125
|
+
ignore_extensions,
|
|
126
|
+
visitor: (filePath: string) => {
|
|
127
|
+
files.push(this.addSourceFileAtPath(filePath))
|
|
128
|
+
},
|
|
129
|
+
})
|
|
130
|
+
|
|
131
|
+
return files
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Recursively adds any .now.ts files in the specified directory, and returns the added
|
|
136
|
+
* files. Glob patterns can be supplied to control which files are added. This will also
|
|
137
|
+
* resolve the dependencies of the added files. If the directory does not exist, an empty
|
|
138
|
+
* array will be returned.
|
|
139
|
+
*/
|
|
140
|
+
addFluentSourceFilesFromDirectory(dir: string): ts.SourceFile[] {
|
|
141
|
+
return this.addSourceFilesFromDirectory(dir, [NOW_FILE_EXTENSION])
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
addFluentSourceFilesAtPaths(paths: string[]): ts.SourceFile[] {
|
|
145
|
+
return this.addSourceFilesAtPaths(paths).filter((file) => file.getBaseName().endsWith(NOW_FILE_EXTENSION))
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
getFluentSourceFilesFromDirectory(dir: string) {
|
|
149
|
+
const directory = this.getDirectoryOrThrow(dir)
|
|
150
|
+
return directory.getDescendantSourceFiles().filter((file) => file.getBaseName().endsWith(NOW_FILE_EXTENSION))
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
getDiagnosticsForFluentFile(fileOrPath: string | ts.SourceFile): TypeScriptDiagnostic[] {
|
|
154
|
+
const file = fileOrPath instanceof ts.SourceFile ? fileOrPath : this.getSourceFileOrThrow(fileOrPath)
|
|
155
|
+
if (!file.getFilePath().endsWith(NOW_FILE_EXTENSION)) {
|
|
156
|
+
return []
|
|
157
|
+
}
|
|
158
|
+
return file
|
|
159
|
+
.getPreEmitDiagnostics()
|
|
160
|
+
.map((diagnostic) => {
|
|
161
|
+
try {
|
|
162
|
+
return new TypeScriptDiagnostic(diagnostic)
|
|
163
|
+
} catch (e) {
|
|
164
|
+
return undefined
|
|
165
|
+
}
|
|
166
|
+
})
|
|
167
|
+
.filter((d) => d) as TypeScriptDiagnostic[]
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
getOrCreateSourceFile(path: string, options?: ts.SourceFileCreateOptions): ts.SourceFile {
|
|
171
|
+
return this.getSourceFile(path) ?? this.createSourceFile(path, '', options)
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
async visitSupportedNodes(
|
|
175
|
+
node: ts.Node,
|
|
176
|
+
visitor: (node: SupportedNode) => void | Promise<void>,
|
|
177
|
+
unsupportedNodeVisitor?: (node: ts.Node) => void | Promise<void>
|
|
178
|
+
): Promise<void> {
|
|
179
|
+
if (node.wasForgotten()) {
|
|
180
|
+
return
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
if (!isSupportedNode(node)) {
|
|
184
|
+
await unsupportedNodeVisitor?.(node)
|
|
185
|
+
return
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// Surprisingly, forEachChild() is not the same as getChildren().forEach() so we need to
|
|
189
|
+
// collect all the children using forEachChild() and then iterate
|
|
190
|
+
const children: ts.Node[] = []
|
|
191
|
+
node.forEachChild((child) => {
|
|
192
|
+
children.push(child)
|
|
193
|
+
})
|
|
194
|
+
|
|
195
|
+
for (const child of children) {
|
|
196
|
+
await this.visitSupportedNodes(child, visitor, unsupportedNodeVisitor)
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
await visitor(node)
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
private getTableSchemaModule(sourceFile: ts.SourceFile) {
|
|
203
|
+
return sourceFile?.getModule('global')?.getModule('Now')?.getModule('Internal')?.getModule('TableSchemas')
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
private getTableInterface(sourceFile: ts.SourceFile) {
|
|
207
|
+
return sourceFile?.getModule('global')?.getModule('Now')?.getModule('Internal')?.getInterface('Tables')
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
interfaceExistsInGlobalDeclaration(tableName: string) {
|
|
211
|
+
const generatedTableFile = this.getSourceFile(this.generatedTableFilePath)
|
|
212
|
+
const tableSchema = this.getTableSchemaModule(generatedTableFile!)
|
|
213
|
+
return tableSchema?.getInterfaces().find((value) => value.getName() === tableName)
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
propertyExistsInGlobalDeclaration(tableName: string) {
|
|
217
|
+
const generatedTableFile = this.getSourceFile(this.generatedTableFilePath)
|
|
218
|
+
const tableInterface = this.getTableInterface(generatedTableFile!)
|
|
219
|
+
return tableInterface?.getProperties().find((value) => value.getName() === tableName)
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
importExistsInGlobalDeclaration(resolvedModuleSpecifier: string) {
|
|
223
|
+
const generatedTableFile = this.getSourceFile(this.generatedTableFilePath)
|
|
224
|
+
return generatedTableFile
|
|
225
|
+
?.getImportDeclarations()
|
|
226
|
+
.find((value) => value.getModuleSpecifierValue() === resolvedModuleSpecifier)
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
getGeneratedTableFile() {
|
|
230
|
+
return this.getSourceFile(this.generatedTableFilePath)
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
addTableInterfacesToGlobalDeclaration(data: { interfaces; properties; imports; namedImports }) {
|
|
234
|
+
const { interfaces, properties, imports, namedImports } = data
|
|
235
|
+
|
|
236
|
+
const generatedTableFile = this.getSourceFile(this.generatedTableFilePath)
|
|
237
|
+
const tableSchema = this.getTableSchemaModule(generatedTableFile!)
|
|
238
|
+
const tableInterface = this.getTableInterface(generatedTableFile!)
|
|
239
|
+
|
|
240
|
+
tableSchema?.addInterfaces(interfaces)
|
|
241
|
+
tableInterface?.addProperties(properties)
|
|
242
|
+
generatedTableFile?.addImportDeclarations(Object.values(imports))
|
|
243
|
+
|
|
244
|
+
namedImports.forEach((i) => {
|
|
245
|
+
i.existingImport.addNamedImport({
|
|
246
|
+
name: i.name,
|
|
247
|
+
})
|
|
248
|
+
})
|
|
249
|
+
|
|
250
|
+
Object.keys(imports).forEach((filePath) => {
|
|
251
|
+
this.sourceFileToRelativeModuleSpecifier[filePath] = imports[filePath].moduleSpecifier
|
|
252
|
+
})
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
removeTableInterfaceFromGlobalDeclaration(sourceFile: string | ts.SourceFile) {
|
|
256
|
+
const filePath = sourceFile instanceof ts.SourceFile ? sourceFile.getFilePath() : sourceFile
|
|
257
|
+
const generatedTableFile = this.getSourceFile(this.generatedTableFilePath)
|
|
258
|
+
const relativeModuleSpecifier = this.sourceFileToRelativeModuleSpecifier[filePath]
|
|
259
|
+
if (relativeModuleSpecifier) {
|
|
260
|
+
const foundDecl = generatedTableFile
|
|
261
|
+
?.getImportDeclarations()
|
|
262
|
+
.find((v) => v.getModuleSpecifierValue() === relativeModuleSpecifier)
|
|
263
|
+
if (foundDecl) {
|
|
264
|
+
const tables = foundDecl.getNamedImports().map((v) => v.getName())
|
|
265
|
+
const tableSchema = this.getTableSchemaModule(generatedTableFile!)
|
|
266
|
+
const tableInterface = this.getTableInterface(generatedTableFile!)
|
|
267
|
+
tables.forEach((table) => {
|
|
268
|
+
tableSchema?.getInterface(table)?.remove()
|
|
269
|
+
tableInterface?.getProperty(table)?.remove()
|
|
270
|
+
})
|
|
271
|
+
foundDecl.remove()
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
isGeneratedTableFile(sourceFile: string | ts.SourceFile): boolean {
|
|
277
|
+
const sourcePath = sourceFile instanceof ts.SourceFile ? sourceFile.getFilePath() : sourceFile
|
|
278
|
+
return this.generatedTableFilePath === sourcePath
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
private createModuleProject(fs: FileSystem, tsConfigPath?: string) {
|
|
282
|
+
return new ts.Project({
|
|
283
|
+
fileSystem: new TsMorphFileSystemWrapper(fs),
|
|
284
|
+
...(tsConfigPath
|
|
285
|
+
? {
|
|
286
|
+
tsConfigFilePath: tsConfigPath,
|
|
287
|
+
// We always want to emit files even if the user config says not to
|
|
288
|
+
compilerOptions: { noEmit: false },
|
|
289
|
+
}
|
|
290
|
+
: {
|
|
291
|
+
compilerOptions: {
|
|
292
|
+
target: ts.ScriptTarget.ES2022,
|
|
293
|
+
module: ts.ModuleKind.ES2022,
|
|
294
|
+
moduleResolution: ts.ModuleResolutionKind.Bundler,
|
|
295
|
+
allowImportingTsExtensions: true,
|
|
296
|
+
declaration: false,
|
|
297
|
+
sourceMap: false,
|
|
298
|
+
skipDefaultLibCheck: true,
|
|
299
|
+
skipLibCheck: true,
|
|
300
|
+
},
|
|
301
|
+
}),
|
|
302
|
+
})
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
compileModule(
|
|
306
|
+
moduleFile: ts.SourceFile,
|
|
307
|
+
fs: FileSystem,
|
|
308
|
+
diagnostics: Diagnostics,
|
|
309
|
+
tsConfigPath?: string
|
|
310
|
+
): string | undefined {
|
|
311
|
+
if (!this.moduleProject) {
|
|
312
|
+
this.moduleProject = this.createModuleProject(fs, tsConfigPath)
|
|
313
|
+
}
|
|
314
|
+
const modulePath = moduleFile.getFilePath()
|
|
315
|
+
let sourceFile: ts.SourceFile | undefined
|
|
316
|
+
if (tsConfigPath) {
|
|
317
|
+
sourceFile = this.moduleProject.getSourceFile(modulePath)
|
|
318
|
+
|
|
319
|
+
if (!sourceFile) {
|
|
320
|
+
diagnostics.error(moduleFile, `${path} not included in project. Check your tsconfig: ${tsConfigPath}`)
|
|
321
|
+
return undefined
|
|
322
|
+
}
|
|
323
|
+
} else {
|
|
324
|
+
sourceFile = this.moduleProject.createSourceFile(modulePath, moduleFile.getFullText(), {
|
|
325
|
+
overwrite: true,
|
|
326
|
+
scriptKind: ts.ScriptKind.TS,
|
|
327
|
+
})
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
sourceFile.getPreEmitDiagnostics().forEach((diagnostic) => {
|
|
331
|
+
try {
|
|
332
|
+
diagnostics.add(new TypeScriptDiagnostic(diagnostic))
|
|
333
|
+
} catch (e) {
|
|
334
|
+
//ignore
|
|
335
|
+
}
|
|
336
|
+
})
|
|
337
|
+
|
|
338
|
+
const emitResult = sourceFile.getEmitOutput()
|
|
339
|
+
emitResult.getDiagnostics().forEach((d) => {
|
|
340
|
+
try {
|
|
341
|
+
const diagnostic = new TypeScriptDiagnostic(d)
|
|
342
|
+
if (diagnostic.level === Diagnostic.Level.Error) {
|
|
343
|
+
diagnostics.add(diagnostic)
|
|
344
|
+
}
|
|
345
|
+
} catch (e) {
|
|
346
|
+
//ignore
|
|
347
|
+
}
|
|
348
|
+
})
|
|
349
|
+
|
|
350
|
+
const output = emitResult.getOutputFiles()[0]
|
|
351
|
+
if (!output) {
|
|
352
|
+
diagnostics.error(moduleFile, `Failed to get emit output for module: ${modulePath}`)
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
return output?.getText()
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
export class TypeScriptDiagnostic extends Diagnostic {
|
|
360
|
+
constructor(private readonly diagnostic: ts.Diagnostic) {
|
|
361
|
+
const message = diagnostic.getMessageText()
|
|
362
|
+
const messageText = typeof message === 'string' ? message : message.getMessageText()
|
|
363
|
+
const start = diagnostic.getStart() ?? 0
|
|
364
|
+
const end = start + (diagnostic.getLength() ?? 0)
|
|
365
|
+
const file = diagnostic.getSourceFile()
|
|
366
|
+
|
|
367
|
+
if (!file) {
|
|
368
|
+
throw new Error(`TypeScript diagnostic does not have a source file (Message: ${messageText})`)
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
super(
|
|
372
|
+
messageText,
|
|
373
|
+
file,
|
|
374
|
+
{ start, end },
|
|
375
|
+
diagnostic.getCode(),
|
|
376
|
+
'ts',
|
|
377
|
+
Diagnostic.Level.fromCategory(diagnostic.getCategory())
|
|
378
|
+
)
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
public override asTypeScriptDiagnostic(): ts.ts.Diagnostic {
|
|
382
|
+
return this.diagnostic.compilerObject
|
|
383
|
+
}
|
|
384
|
+
}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { ZipDeflate, unzipSync, Zip, gzipSync } from 'fflate'
|
|
2
|
+
import { FileSystem, TsMorphFileSystemWrapper } from './fs'
|
|
3
|
+
import { path } from './path'
|
|
4
|
+
|
|
5
|
+
const ZIP_STATUS_FILE = 'status.json'
|
|
6
|
+
|
|
7
|
+
export const unzipAppPackage = async (
|
|
8
|
+
fs: FileSystem,
|
|
9
|
+
zipPath: string,
|
|
10
|
+
targetPath: string
|
|
11
|
+
): Promise<{ files: string[] }> => {
|
|
12
|
+
const zipArrBuf = new Uint8Array(fs.readFileSync(zipPath))
|
|
13
|
+
const unzipResult = unzipSync(zipArrBuf)
|
|
14
|
+
|
|
15
|
+
//remove unnecessary package inventory file if present
|
|
16
|
+
if (unzipResult['package_inventory.csv']) {
|
|
17
|
+
delete unzipResult['package_inventory.csv']
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// check the status of download operation
|
|
21
|
+
if (ZIP_STATUS_FILE in unzipResult) {
|
|
22
|
+
const response = JSON.parse(Buffer.from(unzipResult[ZIP_STATUS_FILE]).toString('utf8'))
|
|
23
|
+
if (response.status === 'error') {
|
|
24
|
+
throw new Error('Application download failed with error, ' + response.message)
|
|
25
|
+
}
|
|
26
|
+
delete unzipResult[ZIP_STATUS_FILE]
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const files = Object.entries(unzipResult).map(([filePath, data]) => {
|
|
30
|
+
const fullPath = path.join(targetPath, filePath)
|
|
31
|
+
|
|
32
|
+
// Create directories if necessary
|
|
33
|
+
const dir = path.dirname(fullPath)
|
|
34
|
+
FileSystem.ensureDirSync(fs, dir)
|
|
35
|
+
|
|
36
|
+
// Write file content to the filesystem
|
|
37
|
+
fs.writeFileSync(fullPath, data)
|
|
38
|
+
|
|
39
|
+
return fullPath
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
return { files }
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export const zipDirectory = async (fs: FileSystem, source: string, destination: string): Promise<string> => {
|
|
46
|
+
return new Promise<string>((resolve, reject) => {
|
|
47
|
+
try {
|
|
48
|
+
const z = new Zip()
|
|
49
|
+
|
|
50
|
+
const outputStream = FileSystem.createWriteStream(fs, destination)
|
|
51
|
+
|
|
52
|
+
z.ondata = (err, data, final) => {
|
|
53
|
+
if (err) {
|
|
54
|
+
outputStream.destroy()
|
|
55
|
+
reject(err)
|
|
56
|
+
} else {
|
|
57
|
+
outputStream.write(data)
|
|
58
|
+
if (final) {
|
|
59
|
+
outputStream.end()
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
outputStream.on('finish', () => {
|
|
65
|
+
resolve(destination)
|
|
66
|
+
})
|
|
67
|
+
|
|
68
|
+
outputStream.on('error', (error) => {
|
|
69
|
+
reject(error)
|
|
70
|
+
})
|
|
71
|
+
|
|
72
|
+
// Using our own glob for browser compatibility
|
|
73
|
+
const wrappedFs = new TsMorphFileSystemWrapper(fs)
|
|
74
|
+
const files = wrappedFs.globSync(['**'], {
|
|
75
|
+
cwd: source,
|
|
76
|
+
})
|
|
77
|
+
|
|
78
|
+
for (const file of files) {
|
|
79
|
+
const fp = path.normalize(file)
|
|
80
|
+
const data = fs.readFileSync(fp)
|
|
81
|
+
const zf = new ZipDeflate(fp.replace(source, ''), { level: 6 })
|
|
82
|
+
z.add(zf)
|
|
83
|
+
zf.push(data, true)
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
z.end()
|
|
87
|
+
} catch (err) {
|
|
88
|
+
reject(err)
|
|
89
|
+
}
|
|
90
|
+
})
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export { gzipSync }
|
package/src/crypto.ts
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
// Node 20+ globally available Crypto Module -OR- Browser Crypto Module
|
|
2
|
+
const globalCrypto = <Crypto>(<any>globalThis.crypto)?.webcrypto || globalThis.crypto
|
|
3
|
+
// Globally available crypto (Node or Browser) -OR- Node Web Crypto (We can get rid of this in April 2025 after Node 18 is EOL)
|
|
4
|
+
// biome-ignore lint/style/noRestrictedImports: This is where we re-export our version of the module
|
|
5
|
+
export const crypto = globalCrypto || require('node:crypto').webcrypto
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import * as lsp from 'vscode-languageserver'
|
|
2
|
+
import * as os from 'os'
|
|
3
|
+
import { ts } from './typescript'
|
|
4
|
+
import type { Logger } from './logger'
|
|
5
|
+
|
|
6
|
+
type PrintOptions = {
|
|
7
|
+
colors?: boolean
|
|
8
|
+
noThrow?: boolean
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export abstract class Diagnostic {
|
|
12
|
+
constructor(
|
|
13
|
+
public readonly message: string,
|
|
14
|
+
public readonly file: ts.SourceFile,
|
|
15
|
+
public readonly position: { start: number; end: number },
|
|
16
|
+
public readonly code: ts.SyntaxKind,
|
|
17
|
+
public readonly source: 'fluent' | 'ts',
|
|
18
|
+
public readonly level: Diagnostic.Level = Diagnostic.Level.Error
|
|
19
|
+
) {}
|
|
20
|
+
|
|
21
|
+
public abstract asTypeScriptDiagnostic(): ts.ts.Diagnostic
|
|
22
|
+
|
|
23
|
+
public asLspDiagnostic(): lsp.Diagnostic {
|
|
24
|
+
const start = this.file.compilerNode.getLineAndCharacterOfPosition(this.position.start)
|
|
25
|
+
const end = this.file.compilerNode.getLineAndCharacterOfPosition(this.position.end)
|
|
26
|
+
|
|
27
|
+
return {
|
|
28
|
+
message: this.message,
|
|
29
|
+
range: { start, end },
|
|
30
|
+
severity: Diagnostic.Level.toSeverity(this.level),
|
|
31
|
+
code: this.code,
|
|
32
|
+
source: this.source,
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
public getFormattedText(colors = true) {
|
|
37
|
+
const host: ts.ts.FormatDiagnosticsHost = {
|
|
38
|
+
getCurrentDirectory: () => this.file.getProject().getFileSystem().getCurrentDirectory(),
|
|
39
|
+
getCanonicalFileName: (fileName) => fileName,
|
|
40
|
+
getNewLine: () => os.EOL,
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return colors
|
|
44
|
+
? ts.ts.formatDiagnosticsWithColorAndContext([this.asTypeScriptDiagnostic()], host)
|
|
45
|
+
: ts.ts.formatDiagnostic(this.asTypeScriptDiagnostic(), host)
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
public print(logger: Logger, { noThrow = false, colors = true }: PrintOptions = {}): void {
|
|
49
|
+
if (this.level === Diagnostic.Level.Error) {
|
|
50
|
+
const errorMessage = this.getFormattedText(colors)
|
|
51
|
+
if (noThrow) {
|
|
52
|
+
logger.error(errorMessage)
|
|
53
|
+
} else {
|
|
54
|
+
logger.error('Diagnostic errors found in source files, fix errors and re-run transform/build')
|
|
55
|
+
throw new Error(errorMessage)
|
|
56
|
+
}
|
|
57
|
+
} else if (this.level === Diagnostic.Level.Warn) {
|
|
58
|
+
logger.warn(this.getFormattedText(colors))
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export namespace Diagnostic {
|
|
64
|
+
export enum Level {
|
|
65
|
+
Error = 1,
|
|
66
|
+
Warn = 2,
|
|
67
|
+
Info = 3,
|
|
68
|
+
Hint = 4,
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export namespace Level {
|
|
72
|
+
export function fromCategory(category: ts.DiagnosticCategory): Level {
|
|
73
|
+
return {
|
|
74
|
+
[ts.DiagnosticCategory.Error]: Diagnostic.Level.Error,
|
|
75
|
+
[ts.DiagnosticCategory.Warning]: Diagnostic.Level.Warn,
|
|
76
|
+
[ts.DiagnosticCategory.Message]: Diagnostic.Level.Info,
|
|
77
|
+
[ts.DiagnosticCategory.Suggestion]: Diagnostic.Level.Hint,
|
|
78
|
+
}[category]
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export function toCategory(level: Level): ts.DiagnosticCategory {
|
|
82
|
+
return {
|
|
83
|
+
[Diagnostic.Level.Error]: ts.DiagnosticCategory.Error,
|
|
84
|
+
[Diagnostic.Level.Warn]: ts.DiagnosticCategory.Warning,
|
|
85
|
+
[Diagnostic.Level.Info]: ts.DiagnosticCategory.Message,
|
|
86
|
+
[Diagnostic.Level.Hint]: ts.DiagnosticCategory.Suggestion,
|
|
87
|
+
}[level]
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export function fromSeverity(severity: lsp.DiagnosticSeverity): Level {
|
|
91
|
+
return {
|
|
92
|
+
[lsp.DiagnosticSeverity.Error]: Diagnostic.Level.Error,
|
|
93
|
+
[lsp.DiagnosticSeverity.Warning]: Diagnostic.Level.Warn,
|
|
94
|
+
[lsp.DiagnosticSeverity.Information]: Diagnostic.Level.Info,
|
|
95
|
+
[lsp.DiagnosticSeverity.Hint]: Diagnostic.Level.Hint,
|
|
96
|
+
}[severity]
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export function toSeverity(level: Level): lsp.DiagnosticSeverity {
|
|
100
|
+
return {
|
|
101
|
+
[Diagnostic.Level.Error]: lsp.DiagnosticSeverity.Error,
|
|
102
|
+
[Diagnostic.Level.Warn]: lsp.DiagnosticSeverity.Warning,
|
|
103
|
+
[Diagnostic.Level.Info]: lsp.DiagnosticSeverity.Information,
|
|
104
|
+
[Diagnostic.Level.Hint]: lsp.DiagnosticSeverity.Hint,
|
|
105
|
+
}[level]
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
import { Diagnostic
|
|
1
|
+
import { Diagnostic } from './diagnostic'
|
|
2
|
+
import type { ts, tsc } from './typescript'
|
|
2
3
|
|
|
3
4
|
export class FluentDiagnostic extends Diagnostic {
|
|
4
5
|
constructor(
|
|
5
|
-
node: ts.Node,
|
|
6
|
+
public readonly node: ts.Node,
|
|
6
7
|
message: string,
|
|
7
8
|
{ level = Diagnostic.Level.Error, code = node.getKind() }: { level?: Diagnostic.Level; code?: number } = {}
|
|
8
9
|
) {
|
|
@@ -20,11 +21,3 @@ export class FluentDiagnostic extends Diagnostic {
|
|
|
20
21
|
}
|
|
21
22
|
}
|
|
22
23
|
}
|
|
23
|
-
|
|
24
|
-
export function findObjectPropertyValue(callExpression: ts.CallExpression, field: string) {
|
|
25
|
-
const property = callExpression
|
|
26
|
-
.getFirstDescendantByKind(ts.SyntaxKind.ObjectLiteralExpression)
|
|
27
|
-
?.getProperty(field) as ts.PropertyAssignment
|
|
28
|
-
|
|
29
|
-
return property?.getInitializer() || callExpression
|
|
30
|
-
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { ts } from './typescript'
|
|
2
|
+
|
|
3
|
+
type FluentDirective = '@fluent-ignore' | '@fluent-disable-sync' | '@fluent-disable-sync-for-file'
|
|
4
|
+
|
|
5
|
+
type CacheEntry = Partial<Record<FluentDirective, boolean>>
|
|
6
|
+
const CACHE = new WeakMap<ts.Node, CacheEntry>()
|
|
7
|
+
function cacheGet(node: ts.Node): CacheEntry {
|
|
8
|
+
return CACHE.get(node) ?? CACHE.set(node, {}).get(node)!
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Determines if the specified node or any of its ancestors have the specified directive in
|
|
13
|
+
* one of their leading comment ranges. Avoids regex for optimal performance.
|
|
14
|
+
*/
|
|
15
|
+
export function hasDirective(node: ts.Node, directive: FluentDirective): boolean {
|
|
16
|
+
const cache = cacheGet(node)
|
|
17
|
+
if (cache[directive] !== undefined) {
|
|
18
|
+
return cache[directive]
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
for (const comment of getComments(node)) {
|
|
22
|
+
const text = comment.getText()
|
|
23
|
+
const idx = text.indexOf(directive)
|
|
24
|
+
if (idx >= 0) {
|
|
25
|
+
return (cache[directive] =
|
|
26
|
+
isDirectiveBoundary(text.charCodeAt(idx - 1)) &&
|
|
27
|
+
isDirectiveBoundary(text.charCodeAt(idx + directive.length)))
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const parent = node.getParent()
|
|
32
|
+
return (cache[directive] = !!parent && hasDirective(parent, directive))
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function getComments(node: ts.Node): ts.CommentRange[] | ts.CommentStatement[] {
|
|
36
|
+
if (!ts.Node.isSourceFile(node)) {
|
|
37
|
+
return node.getLeadingCommentRanges()
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const top = node.getFirstChildIfKind(ts.SyntaxKind.SyntaxList)?.getFirstChild()
|
|
41
|
+
return ts.Node.isCommentStatement(top) ? [top] : []
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const DIRECTIVE_CHARS = {
|
|
45
|
+
'-': (c: number) => c === 45,
|
|
46
|
+
'@': (c: number) => c === 64,
|
|
47
|
+
'A-Z': (c: number) => c >= 65 && c <= 90,
|
|
48
|
+
'a-z': (c: number) => c >= 97 && c <= 122,
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function isDirectiveBoundary(code: number): boolean {
|
|
52
|
+
if (isNaN(code)) {
|
|
53
|
+
return true
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
for (const test of Object.values(DIRECTIVE_CHARS)) {
|
|
57
|
+
if (test(code)) {
|
|
58
|
+
return false
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return true
|
|
63
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { ts } from './typescript'
|
|
2
|
+
import type { ProjectFile } from './plugins'
|
|
3
|
+
|
|
4
|
+
export const NOW_FILE_EXTENSION = '.now.ts'
|
|
5
|
+
|
|
6
|
+
export function isFluentNode(node: ts.Node): boolean {
|
|
7
|
+
return isFluentFile(node.getSourceFile())
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function isFluentFile(file: ts.SourceFile | ProjectFile | string): boolean {
|
|
11
|
+
const path = ts.Node.isNode(file) ? file.getFilePath() : typeof file === 'string' ? file : file.getPath()
|
|
12
|
+
return path.endsWith(NOW_FILE_EXTENSION)
|
|
13
|
+
}
|