tsifdef 1.1.8
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/CHANGELOG.md +25 -0
- package/CHANGELOG.zh-CN.md +22 -0
- package/CONTRIBUTING.md +52 -0
- package/INTEGRATION.md +166 -0
- package/INTEGRATION.zh-CN.md +159 -0
- package/LICENSE +210 -0
- package/README.md +150 -0
- package/README.zh-CN.md +141 -0
- package/SECURITY.md +15 -0
- package/assets/icon-mono.svg +15 -0
- package/assets/icon.png +0 -0
- package/assets/icon.svg +16 -0
- package/dist/cli/build.d.ts +70 -0
- package/dist/cli/build.js +298 -0
- package/dist/cli/config.d.ts +30 -0
- package/dist/cli/config.js +135 -0
- package/dist/cli/diagnostics.d.ts +3 -0
- package/dist/cli/diagnostics.js +31 -0
- package/dist/cli/index.d.ts +6 -0
- package/dist/cli/index.js +35 -0
- package/dist/cli/main.d.ts +9 -0
- package/dist/cli/main.js +165 -0
- package/dist/cli/precompile.d.ts +38 -0
- package/dist/cli/precompile.js +192 -0
- package/dist/cli/source-files.d.ts +10 -0
- package/dist/cli/source-files.js +75 -0
- package/dist/cli/watch.d.ts +34 -0
- package/dist/cli/watch.js +183 -0
- package/dist/core/conditional.d.ts +18 -0
- package/dist/core/conditional.js +126 -0
- package/dist/core/expression.d.ts +44 -0
- package/dist/core/expression.js +224 -0
- package/dist/core/index.d.ts +7 -0
- package/dist/core/index.js +37 -0
- package/dist/core/projection.d.ts +15 -0
- package/dist/core/projection.js +99 -0
- package/dist/core/scanner.d.ts +33 -0
- package/dist/core/scanner.js +344 -0
- package/dist/eslint/package.d.ts +10 -0
- package/dist/eslint/package.js +55 -0
- package/dist/eslint/parser.d.ts +35 -0
- package/dist/eslint/parser.js +59 -0
- package/dist/eslint/plugin.d.ts +78 -0
- package/dist/eslint/plugin.js +229 -0
- package/dist/eslint/projection.d.ts +11 -0
- package/dist/eslint/projection.js +116 -0
- package/dist/tsserver/host-projection.d.ts +45 -0
- package/dist/tsserver/host-projection.js +160 -0
- package/dist/tsserver/index.d.ts +3 -0
- package/dist/tsserver/index.js +32 -0
- package/dist/tsserver/plugin.d.ts +15 -0
- package/dist/tsserver/plugin.js +129 -0
- package/dist/tsserver/project-controller.d.ts +37 -0
- package/dist/tsserver/project-controller.js +60 -0
- package/dist/version.d.ts +3 -0
- package/dist/version.js +20 -0
- package/dist/vscode/document-analysis.d.ts +62 -0
- package/dist/vscode/document-analysis.js +111 -0
- package/dist/vscode/extension.d.ts +133 -0
- package/dist/vscode/extension.js +241 -0
- package/dist/vscode/host.d.ts +79 -0
- package/dist/vscode/host.js +16 -0
- package/dist/vscode/index.d.ts +7 -0
- package/dist/vscode/index.js +36 -0
- package/dist/vscode/macro-presentation.d.ts +43 -0
- package/dist/vscode/macro-presentation.js +130 -0
- package/dist/vscode/package-profile.d.ts +27 -0
- package/dist/vscode/package-profile.js +142 -0
- package/dist/vscode/profile-state.d.ts +22 -0
- package/dist/vscode/profile-state.js +81 -0
- package/package.json +129 -0
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// Copyright (C) 2026 Tencent. All rights reserved.
|
|
3
|
+
//
|
|
4
|
+
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
// you may not use this file except in compliance with the License.
|
|
6
|
+
// You may obtain a copy of the License at
|
|
7
|
+
//
|
|
8
|
+
// http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
//
|
|
10
|
+
// Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
// See the License for the specific language governing permissions and
|
|
14
|
+
// limitations under the License.
|
|
15
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
16
|
+
exports.watchProject = watchProject;
|
|
17
|
+
const node_fs_1 = require("node:fs");
|
|
18
|
+
const node_path_1 = require("node:path");
|
|
19
|
+
const index_js_1 = require("../core/index.js");
|
|
20
|
+
const build_js_1 = require("./build.js");
|
|
21
|
+
const config_js_1 = require("./config.js");
|
|
22
|
+
const diagnostics_js_1 = require("./diagnostics.js");
|
|
23
|
+
const source_files_js_1 = require("./source-files.js");
|
|
24
|
+
const macroFilePattern = /(?:\.d)?\.(?:ts|tsx|mts|cts)$/i;
|
|
25
|
+
/**
|
|
26
|
+
* Watch mode for projected compilation. A `createWatchCompilerHost` drives
|
|
27
|
+
* incremental rebuilds with the same equal-length masking as one-shot builds.
|
|
28
|
+
* The Profile file is watched separately; any change tears down the current
|
|
29
|
+
* WatchProgram and rebuilds it in full under the new Profile.
|
|
30
|
+
*/
|
|
31
|
+
async function watchProject(options) {
|
|
32
|
+
const ts = (await import("typescript")).default;
|
|
33
|
+
const projectRoot = (0, node_path_1.resolve)(options.projectRoot);
|
|
34
|
+
const configPath = (0, node_path_1.resolve)(projectRoot, options.project);
|
|
35
|
+
const configFile = ts.readConfigFile(configPath, ts.sys.readFile);
|
|
36
|
+
if (configFile.error !== undefined)
|
|
37
|
+
throw new Error(formatConfigDiagnostic(ts, configFile.error));
|
|
38
|
+
const parsed = ts.parseJsonConfigFileContent(configFile.config, ts.sys, (0, node_path_1.dirname)(configPath), undefined, configPath);
|
|
39
|
+
if (parsed.errors.length > 0) {
|
|
40
|
+
throw new Error(parsed.errors.map((item) => formatConfigDiagnostic(ts, item)).join("\n"));
|
|
41
|
+
}
|
|
42
|
+
if (options.compilerOptionsOverride !== undefined) {
|
|
43
|
+
Object.assign(parsed.options, options.compilerOptionsOverride);
|
|
44
|
+
}
|
|
45
|
+
assertWatchSupported(parsed.options, parsed.projectReferences);
|
|
46
|
+
let currentWatch;
|
|
47
|
+
let profileWatcher;
|
|
48
|
+
let reloadTimer;
|
|
49
|
+
let closed = false;
|
|
50
|
+
const startWatchProgram = (definitions) => {
|
|
51
|
+
const macroDiagnostics = new Map();
|
|
52
|
+
const readProjected = (fileName) => {
|
|
53
|
+
if (!macroFilePattern.test(fileName))
|
|
54
|
+
return ts.sys.readFile(fileName);
|
|
55
|
+
const absolute = (0, node_path_1.resolve)(fileName);
|
|
56
|
+
let bytes;
|
|
57
|
+
try {
|
|
58
|
+
bytes = (0, node_fs_1.readFileSync)(absolute);
|
|
59
|
+
}
|
|
60
|
+
catch {
|
|
61
|
+
macroDiagnostics.delete(absolute);
|
|
62
|
+
return undefined;
|
|
63
|
+
}
|
|
64
|
+
const result = (0, index_js_1.projectSource)((0, source_files_js_1.decodeTypeScriptText)(bytes), definitions);
|
|
65
|
+
if (result.diagnostics.length > 0)
|
|
66
|
+
macroDiagnostics.set(absolute, result.diagnostics);
|
|
67
|
+
else
|
|
68
|
+
macroDiagnostics.delete(absolute);
|
|
69
|
+
if (options.emitProjectionDir !== undefined && isInsideRoot(projectRoot, absolute)) {
|
|
70
|
+
const dumpPath = (0, node_path_1.resolve)(options.emitProjectionDir, (0, node_path_1.relative)(projectRoot, absolute));
|
|
71
|
+
try {
|
|
72
|
+
(0, node_fs_1.mkdirSync)((0, node_path_1.dirname)(dumpPath), { recursive: true });
|
|
73
|
+
(0, node_fs_1.writeFileSync)(dumpPath, result.projectedText, "utf8");
|
|
74
|
+
}
|
|
75
|
+
catch {
|
|
76
|
+
// Projection dump is best-effort.
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
return result.projectedText;
|
|
80
|
+
};
|
|
81
|
+
const host = ts.createWatchCompilerHost(configPath, options.compilerOptionsOverride ?? undefined, ts.sys, ts.createEmitAndSemanticDiagnosticsBuilderProgram, (diagnostic) => {
|
|
82
|
+
process.stderr.write(`${ts.flattenDiagnosticMessageText(diagnostic.messageText, ts.sys.newLine)}${ts.sys.newLine}`);
|
|
83
|
+
}, () => {
|
|
84
|
+
// Suppress the periodic "watching for changes" status lines.
|
|
85
|
+
});
|
|
86
|
+
const originalRead = host.readFile?.bind(host);
|
|
87
|
+
host.readFile = (fileName, encoding) => {
|
|
88
|
+
const projected = readProjected(fileName);
|
|
89
|
+
if (projected !== undefined)
|
|
90
|
+
return projected;
|
|
91
|
+
return originalRead?.(fileName, encoding);
|
|
92
|
+
};
|
|
93
|
+
// Take over emit so we can restore inlineSources, collect outputs, and skip
|
|
94
|
+
// emit for files with macro-structure errors without killing the watcher.
|
|
95
|
+
host.afterProgramCreate = (builderProgram) => {
|
|
96
|
+
const compilerOptions = builderProgram.getCompilerOptions();
|
|
97
|
+
const restoreSources = compilerOptions.inlineSources === true;
|
|
98
|
+
const outputFiles = [];
|
|
99
|
+
const preEmit = ts.getPreEmitDiagnostics(builderProgram.getProgram());
|
|
100
|
+
const hasTypeErrors = preEmit.some((d) => d.category === ts.DiagnosticCategory.Error);
|
|
101
|
+
for (const diagnostic of preEmit) {
|
|
102
|
+
process.stderr.write((0, diagnostics_js_1.formatCliDiagnostics)(ts, [diagnostic], projectRoot));
|
|
103
|
+
}
|
|
104
|
+
for (const [file, diagnostics] of macroDiagnostics) {
|
|
105
|
+
for (const diagnostic of diagnostics) {
|
|
106
|
+
process.stderr.write(`${file}:${diagnostic.range.start}: ${diagnostic.message}${ts.sys.newLine}`);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
const blockEmit = macroDiagnostics.size > 0 || (hasTypeErrors && compilerOptions.noEmitOnError === true);
|
|
110
|
+
if (!blockEmit && compilerOptions.noEmit !== true) {
|
|
111
|
+
builderProgram.emit(undefined, (fileName, text, writeByteOrderMark) => {
|
|
112
|
+
const output = restoreSources ? (0, build_js_1.restoreInlineSourcesFor)(fileName, text) : text;
|
|
113
|
+
ts.sys.writeFile(fileName, output, writeByteOrderMark);
|
|
114
|
+
outputFiles.push((0, node_path_1.resolve)(fileName));
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
outputFiles.sort((left, right) => left.localeCompare(right, "en"));
|
|
118
|
+
options.onBuild?.({
|
|
119
|
+
outputFiles,
|
|
120
|
+
macroDiagnostics: new Map(macroDiagnostics),
|
|
121
|
+
hasErrors: hasTypeErrors || macroDiagnostics.size > 0,
|
|
122
|
+
});
|
|
123
|
+
};
|
|
124
|
+
const watch = ts.createWatchProgram(host);
|
|
125
|
+
return { close: () => watch.close() };
|
|
126
|
+
};
|
|
127
|
+
const reloadProfile = () => {
|
|
128
|
+
if (closed)
|
|
129
|
+
return;
|
|
130
|
+
void (0, config_js_1.loadProfileFile)(options.profilePath)
|
|
131
|
+
.then((profile) => {
|
|
132
|
+
if (closed)
|
|
133
|
+
return;
|
|
134
|
+
currentWatch?.close();
|
|
135
|
+
currentWatch = startWatchProgram(profile.definitions);
|
|
136
|
+
options.onProfileReload?.(profile);
|
|
137
|
+
})
|
|
138
|
+
.catch((error) => {
|
|
139
|
+
process.stderr.write(`${error instanceof Error ? error.message : String(error)}${ts.sys.newLine}`);
|
|
140
|
+
});
|
|
141
|
+
};
|
|
142
|
+
const initialProfile = await (0, config_js_1.loadProfileFile)(options.profilePath);
|
|
143
|
+
currentWatch = startWatchProgram(initialProfile.definitions);
|
|
144
|
+
// Watch the Profile file itself; tsc never watches non-TypeScript inputs.
|
|
145
|
+
try {
|
|
146
|
+
profileWatcher = (0, node_fs_1.watch)(options.profilePath, () => {
|
|
147
|
+
if (reloadTimer !== undefined)
|
|
148
|
+
clearTimeout(reloadTimer);
|
|
149
|
+
// Debounce editor save bursts (write + rename can fire several events).
|
|
150
|
+
reloadTimer = setTimeout(reloadProfile, 50);
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
catch {
|
|
154
|
+
// If the Profile file cannot be watched (e.g. on some platforms), the CLI
|
|
155
|
+
// still watches sources; the user can restart to pick up a Profile change.
|
|
156
|
+
}
|
|
157
|
+
return {
|
|
158
|
+
close: () => {
|
|
159
|
+
closed = true;
|
|
160
|
+
if (reloadTimer !== undefined)
|
|
161
|
+
clearTimeout(reloadTimer);
|
|
162
|
+
profileWatcher?.close();
|
|
163
|
+
currentWatch?.close();
|
|
164
|
+
},
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
/** Keep watch-mode support aligned with one-shot build. */
|
|
168
|
+
function assertWatchSupported(options, projectReferences) {
|
|
169
|
+
if (typeof options.outFile === "string" && options.outFile !== "") {
|
|
170
|
+
throw new build_js_1.BuildUnsupportedError("tsifdef build does not support 'outFile'; per-file projection is incompatible with single-file bundle output.");
|
|
171
|
+
}
|
|
172
|
+
if (projectReferences !== undefined && projectReferences.length > 0) {
|
|
173
|
+
throw new build_js_1.BuildUnsupportedError("tsifdef build does not support project references (tsc -b / composite solutions); run tsifdef build per referenced project instead.");
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
function isInsideRoot(root, path) {
|
|
177
|
+
const value = (0, node_path_1.relative)(root, path);
|
|
178
|
+
return value !== ".." && !value.startsWith(`..${node_path_1.sep}`) && !(0, node_path_1.isAbsolute)(value);
|
|
179
|
+
}
|
|
180
|
+
function formatConfigDiagnostic(ts, diagnostic) {
|
|
181
|
+
return `TS${diagnostic.code}: ${ts.flattenDiagnosticMessageText(diagnostic.messageText, "\n")}`;
|
|
182
|
+
}
|
|
183
|
+
//# sourceMappingURL=watch.js.map
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { type ExpressionDiagnostic, type MacroDefinitions } from "./expression.js";
|
|
2
|
+
import { type Directive, type ScannerDiagnostic, type SourceRange } from "./scanner.js";
|
|
3
|
+
export type ConditionalDiagnosticCode = "active-error" | "unexpected-directive-argument";
|
|
4
|
+
export interface ConditionalDiagnostic {
|
|
5
|
+
readonly code: ConditionalDiagnosticCode;
|
|
6
|
+
readonly message: string;
|
|
7
|
+
readonly range: SourceRange;
|
|
8
|
+
}
|
|
9
|
+
export type MacroDiagnostic = ScannerDiagnostic | ExpressionDiagnostic | ConditionalDiagnostic;
|
|
10
|
+
export interface ConditionalAnalysisResult {
|
|
11
|
+
readonly directives: readonly Directive[];
|
|
12
|
+
readonly directiveRanges: readonly SourceRange[];
|
|
13
|
+
readonly inactiveRanges: readonly SourceRange[];
|
|
14
|
+
readonly diagnostics: readonly MacroDiagnostic[];
|
|
15
|
+
}
|
|
16
|
+
/** Analyze conditional groups without modifying the source text. */
|
|
17
|
+
export declare function analyzeConditionals(source: string, definitions: MacroDefinitions): ConditionalAnalysisResult;
|
|
18
|
+
//# sourceMappingURL=conditional.d.ts.map
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// Copyright (C) 2026 Tencent. All rights reserved.
|
|
3
|
+
//
|
|
4
|
+
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
// you may not use this file except in compliance with the License.
|
|
6
|
+
// You may obtain a copy of the License at
|
|
7
|
+
//
|
|
8
|
+
// http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
//
|
|
10
|
+
// Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
// See the License for the specific language governing permissions and
|
|
14
|
+
// limitations under the License.
|
|
15
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
16
|
+
exports.analyzeConditionals = analyzeConditionals;
|
|
17
|
+
const expression_js_1 = require("./expression.js");
|
|
18
|
+
const scanner_js_1 = require("./scanner.js");
|
|
19
|
+
/** Analyze conditional groups without modifying the source text. */
|
|
20
|
+
function analyzeConditionals(source, definitions) {
|
|
21
|
+
const scanned = (0, scanner_js_1.scanDirectives)(source);
|
|
22
|
+
const diagnostics = [...scanned.diagnostics];
|
|
23
|
+
const inactiveRanges = [];
|
|
24
|
+
const stack = [];
|
|
25
|
+
let cursor = 0;
|
|
26
|
+
let currentActive = true;
|
|
27
|
+
for (const directive of scanned.directives) {
|
|
28
|
+
if (!currentActive) {
|
|
29
|
+
appendRange(inactiveRanges, { start: cursor, end: directive.range.start });
|
|
30
|
+
}
|
|
31
|
+
switch (directive.kind) {
|
|
32
|
+
case "if": {
|
|
33
|
+
const condition = evaluateDirectiveExpression(directive, definitions, diagnostics);
|
|
34
|
+
stack.push({
|
|
35
|
+
parentActive: currentActive,
|
|
36
|
+
branchTaken: condition,
|
|
37
|
+
currentActive: currentActive && condition,
|
|
38
|
+
sawElse: false,
|
|
39
|
+
});
|
|
40
|
+
currentActive = stack.at(-1).currentActive;
|
|
41
|
+
break;
|
|
42
|
+
}
|
|
43
|
+
case "elif": {
|
|
44
|
+
const condition = evaluateDirectiveExpression(directive, definitions, diagnostics);
|
|
45
|
+
const frame = stack.at(-1);
|
|
46
|
+
if (frame !== undefined) {
|
|
47
|
+
frame.currentActive =
|
|
48
|
+
!frame.sawElse && frame.parentActive && !frame.branchTaken && condition;
|
|
49
|
+
if (!frame.sawElse) {
|
|
50
|
+
frame.branchTaken ||= condition;
|
|
51
|
+
}
|
|
52
|
+
currentActive = frame.currentActive;
|
|
53
|
+
}
|
|
54
|
+
break;
|
|
55
|
+
}
|
|
56
|
+
case "else": {
|
|
57
|
+
reportUnexpectedArgument(directive, diagnostics);
|
|
58
|
+
const frame = stack.at(-1);
|
|
59
|
+
if (frame !== undefined) {
|
|
60
|
+
frame.currentActive =
|
|
61
|
+
!frame.sawElse && frame.parentActive && !frame.branchTaken;
|
|
62
|
+
if (!frame.sawElse) {
|
|
63
|
+
frame.sawElse = true;
|
|
64
|
+
frame.branchTaken = true;
|
|
65
|
+
}
|
|
66
|
+
currentActive = frame.currentActive;
|
|
67
|
+
}
|
|
68
|
+
break;
|
|
69
|
+
}
|
|
70
|
+
case "endif":
|
|
71
|
+
reportUnexpectedArgument(directive, diagnostics);
|
|
72
|
+
if (stack.length > 0) {
|
|
73
|
+
stack.pop();
|
|
74
|
+
currentActive = stack.at(-1)?.currentActive ?? true;
|
|
75
|
+
}
|
|
76
|
+
break;
|
|
77
|
+
case "error":
|
|
78
|
+
if (currentActive) {
|
|
79
|
+
diagnostics.push({
|
|
80
|
+
code: "active-error",
|
|
81
|
+
message: directive.argument || "#error directive is active.",
|
|
82
|
+
range: directive.argumentRange ?? directive.keywordRange,
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
break;
|
|
86
|
+
}
|
|
87
|
+
cursor = directive.range.end;
|
|
88
|
+
}
|
|
89
|
+
if (!currentActive) {
|
|
90
|
+
appendRange(inactiveRanges, { start: cursor, end: source.length });
|
|
91
|
+
}
|
|
92
|
+
diagnostics.sort((left, right) => left.range.start - right.range.start);
|
|
93
|
+
return {
|
|
94
|
+
directives: scanned.directives,
|
|
95
|
+
directiveRanges: scanned.directiveRanges,
|
|
96
|
+
inactiveRanges,
|
|
97
|
+
diagnostics,
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
function evaluateDirectiveExpression(directive, definitions, diagnostics) {
|
|
101
|
+
const result = (0, expression_js_1.evaluateMacroExpression)(directive.argument, definitions, directive.argumentRange?.start ?? directive.keywordRange.end);
|
|
102
|
+
diagnostics.push(...result.diagnostics);
|
|
103
|
+
return result.value;
|
|
104
|
+
}
|
|
105
|
+
function reportUnexpectedArgument(directive, diagnostics) {
|
|
106
|
+
if (directive.argumentRange !== null) {
|
|
107
|
+
diagnostics.push({
|
|
108
|
+
code: "unexpected-directive-argument",
|
|
109
|
+
message: `#${directive.kind} does not accept an argument.`,
|
|
110
|
+
range: directive.argumentRange,
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
function appendRange(ranges, range) {
|
|
115
|
+
if (range.start >= range.end) {
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
const previous = ranges.at(-1);
|
|
119
|
+
if (previous !== undefined && previous.end === range.start) {
|
|
120
|
+
ranges[ranges.length - 1] = { start: previous.start, end: range.end };
|
|
121
|
+
}
|
|
122
|
+
else {
|
|
123
|
+
ranges.push(range);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
//# sourceMappingURL=conditional.js.map
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import type { SourceRange } from "./scanner.js";
|
|
2
|
+
export type MacroDefinitions = Readonly<Record<string, boolean>>;
|
|
3
|
+
export type MacroExpression = IdentifierExpression | DefinedExpression | NotExpression | LogicalExpression;
|
|
4
|
+
export interface IdentifierExpression {
|
|
5
|
+
readonly kind: "identifier";
|
|
6
|
+
readonly name: string;
|
|
7
|
+
readonly range: SourceRange;
|
|
8
|
+
}
|
|
9
|
+
export interface DefinedExpression {
|
|
10
|
+
readonly kind: "defined";
|
|
11
|
+
readonly name: string;
|
|
12
|
+
readonly range: SourceRange;
|
|
13
|
+
readonly nameRange: SourceRange;
|
|
14
|
+
}
|
|
15
|
+
export interface NotExpression {
|
|
16
|
+
readonly kind: "not";
|
|
17
|
+
readonly operand: MacroExpression;
|
|
18
|
+
readonly range: SourceRange;
|
|
19
|
+
}
|
|
20
|
+
export interface LogicalExpression {
|
|
21
|
+
readonly kind: "logical";
|
|
22
|
+
readonly operator: "&&" | "||";
|
|
23
|
+
readonly left: MacroExpression;
|
|
24
|
+
readonly right: MacroExpression;
|
|
25
|
+
readonly range: SourceRange;
|
|
26
|
+
}
|
|
27
|
+
export type ExpressionDiagnosticCode = "expected-expression" | "expected-defined-parenthesis" | "expected-defined-name" | "expected-closing-parenthesis" | "unexpected-token";
|
|
28
|
+
export interface ExpressionDiagnostic {
|
|
29
|
+
readonly code: ExpressionDiagnosticCode;
|
|
30
|
+
readonly message: string;
|
|
31
|
+
readonly range: SourceRange;
|
|
32
|
+
}
|
|
33
|
+
export interface ParseExpressionResult {
|
|
34
|
+
readonly expression: MacroExpression | null;
|
|
35
|
+
readonly diagnostics: readonly ExpressionDiagnostic[];
|
|
36
|
+
}
|
|
37
|
+
export interface EvaluateExpressionResult extends ParseExpressionResult {
|
|
38
|
+
readonly value: boolean;
|
|
39
|
+
}
|
|
40
|
+
/** Parse one macro expression. Offsets in the result include `baseOffset`. */
|
|
41
|
+
export declare function parseMacroExpression(source: string, baseOffset?: number): ParseExpressionResult;
|
|
42
|
+
/** Parse and evaluate one macro expression against a Profile definition map. */
|
|
43
|
+
export declare function evaluateMacroExpression(source: string, definitions: MacroDefinitions, baseOffset?: number): EvaluateExpressionResult;
|
|
44
|
+
//# sourceMappingURL=expression.d.ts.map
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// Copyright (C) 2026 Tencent. All rights reserved.
|
|
3
|
+
//
|
|
4
|
+
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
// you may not use this file except in compliance with the License.
|
|
6
|
+
// You may obtain a copy of the License at
|
|
7
|
+
//
|
|
8
|
+
// http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
//
|
|
10
|
+
// Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
// See the License for the specific language governing permissions and
|
|
14
|
+
// limitations under the License.
|
|
15
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
16
|
+
exports.parseMacroExpression = parseMacroExpression;
|
|
17
|
+
exports.evaluateMacroExpression = evaluateMacroExpression;
|
|
18
|
+
/** Parse one macro expression. Offsets in the result include `baseOffset`. */
|
|
19
|
+
function parseMacroExpression(source, baseOffset = 0) {
|
|
20
|
+
const parser = new ExpressionParser(tokenize(source, baseOffset));
|
|
21
|
+
return parser.parse();
|
|
22
|
+
}
|
|
23
|
+
/** Parse and evaluate one macro expression against a Profile definition map. */
|
|
24
|
+
function evaluateMacroExpression(source, definitions, baseOffset = 0) {
|
|
25
|
+
const parsed = parseMacroExpression(source, baseOffset);
|
|
26
|
+
if (parsed.expression === null || parsed.diagnostics.length > 0) {
|
|
27
|
+
return { ...parsed, value: false };
|
|
28
|
+
}
|
|
29
|
+
const value = evaluateNode(parsed.expression, definitions);
|
|
30
|
+
return { expression: parsed.expression, diagnostics: [], value };
|
|
31
|
+
}
|
|
32
|
+
class ExpressionParser {
|
|
33
|
+
tokens;
|
|
34
|
+
offset = 0;
|
|
35
|
+
diagnostics = [];
|
|
36
|
+
constructor(tokens) {
|
|
37
|
+
this.tokens = tokens;
|
|
38
|
+
}
|
|
39
|
+
parse() {
|
|
40
|
+
const expression = this.parseOr();
|
|
41
|
+
if (expression !== null && this.current().kind !== "eof") {
|
|
42
|
+
this.addDiagnostic("unexpected-token", `Unexpected token '${this.current().text}'.`, this.current().range);
|
|
43
|
+
}
|
|
44
|
+
return {
|
|
45
|
+
expression: this.diagnostics.length === 0 ? expression : null,
|
|
46
|
+
diagnostics: this.diagnostics,
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
parseOr() {
|
|
50
|
+
let left = this.parseAnd();
|
|
51
|
+
while (left !== null && this.current().kind === "or") {
|
|
52
|
+
this.advance();
|
|
53
|
+
const right = this.parseAnd();
|
|
54
|
+
if (right === null) {
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
57
|
+
left = {
|
|
58
|
+
kind: "logical",
|
|
59
|
+
operator: "||",
|
|
60
|
+
left,
|
|
61
|
+
right,
|
|
62
|
+
range: { start: left.range.start, end: right.range.end },
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
return left;
|
|
66
|
+
}
|
|
67
|
+
parseAnd() {
|
|
68
|
+
let left = this.parseUnary();
|
|
69
|
+
while (left !== null && this.current().kind === "and") {
|
|
70
|
+
this.advance();
|
|
71
|
+
const right = this.parseUnary();
|
|
72
|
+
if (right === null) {
|
|
73
|
+
return null;
|
|
74
|
+
}
|
|
75
|
+
left = {
|
|
76
|
+
kind: "logical",
|
|
77
|
+
operator: "&&",
|
|
78
|
+
left,
|
|
79
|
+
right,
|
|
80
|
+
range: { start: left.range.start, end: right.range.end },
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
return left;
|
|
84
|
+
}
|
|
85
|
+
parseUnary() {
|
|
86
|
+
if (this.current().kind !== "not") {
|
|
87
|
+
return this.parsePrimary();
|
|
88
|
+
}
|
|
89
|
+
const operator = this.advance();
|
|
90
|
+
const operand = this.parseUnary();
|
|
91
|
+
return operand === null
|
|
92
|
+
? null
|
|
93
|
+
: {
|
|
94
|
+
kind: "not",
|
|
95
|
+
operand,
|
|
96
|
+
range: { start: operator.range.start, end: operand.range.end },
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
parsePrimary() {
|
|
100
|
+
const token = this.current();
|
|
101
|
+
if (token.kind === "identifier") {
|
|
102
|
+
this.advance();
|
|
103
|
+
return token.text === "defined"
|
|
104
|
+
? this.parseDefined(token)
|
|
105
|
+
: { kind: "identifier", name: token.text, range: token.range };
|
|
106
|
+
}
|
|
107
|
+
if (token.kind === "open-parenthesis") {
|
|
108
|
+
this.advance();
|
|
109
|
+
const expression = this.parseOr();
|
|
110
|
+
if (expression === null) {
|
|
111
|
+
return null;
|
|
112
|
+
}
|
|
113
|
+
if (this.current().kind !== "close-parenthesis") {
|
|
114
|
+
this.addDiagnostic("expected-closing-parenthesis", "Expected ')' to close macro expression.", this.current().range);
|
|
115
|
+
return null;
|
|
116
|
+
}
|
|
117
|
+
this.advance();
|
|
118
|
+
return expression;
|
|
119
|
+
}
|
|
120
|
+
this.addDiagnostic("expected-expression", "Expected a macro name, defined(NAME), '!', or '('.", token.range);
|
|
121
|
+
if (token.kind !== "eof") {
|
|
122
|
+
this.advance();
|
|
123
|
+
}
|
|
124
|
+
return null;
|
|
125
|
+
}
|
|
126
|
+
parseDefined(definedToken) {
|
|
127
|
+
if (this.current().kind !== "open-parenthesis") {
|
|
128
|
+
this.addDiagnostic("expected-defined-parenthesis", "Expected '(' after defined.", this.current().range);
|
|
129
|
+
return null;
|
|
130
|
+
}
|
|
131
|
+
this.advance();
|
|
132
|
+
const name = this.current();
|
|
133
|
+
if (name.kind !== "identifier") {
|
|
134
|
+
this.addDiagnostic("expected-defined-name", "Expected a macro name inside defined(...).", name.range);
|
|
135
|
+
return null;
|
|
136
|
+
}
|
|
137
|
+
this.advance();
|
|
138
|
+
const closing = this.current();
|
|
139
|
+
if (closing.kind !== "close-parenthesis") {
|
|
140
|
+
this.addDiagnostic("expected-closing-parenthesis", "Expected ')' after the macro name in defined(...).", closing.range);
|
|
141
|
+
return null;
|
|
142
|
+
}
|
|
143
|
+
this.advance();
|
|
144
|
+
return {
|
|
145
|
+
kind: "defined",
|
|
146
|
+
name: name.text,
|
|
147
|
+
nameRange: name.range,
|
|
148
|
+
range: { start: definedToken.range.start, end: closing.range.end },
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
current() {
|
|
152
|
+
return this.tokens[this.offset] ?? this.tokens[this.tokens.length - 1];
|
|
153
|
+
}
|
|
154
|
+
advance() {
|
|
155
|
+
const token = this.current();
|
|
156
|
+
if (token.kind !== "eof") {
|
|
157
|
+
this.offset += 1;
|
|
158
|
+
}
|
|
159
|
+
return token;
|
|
160
|
+
}
|
|
161
|
+
addDiagnostic(code, message, range) {
|
|
162
|
+
this.diagnostics.push({ code, message, range });
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
function tokenize(source, baseOffset) {
|
|
166
|
+
const tokens = [];
|
|
167
|
+
let offset = 0;
|
|
168
|
+
while (offset < source.length) {
|
|
169
|
+
const character = source[offset];
|
|
170
|
+
if (/\s/.test(character)) {
|
|
171
|
+
offset += 1;
|
|
172
|
+
continue;
|
|
173
|
+
}
|
|
174
|
+
const start = offset;
|
|
175
|
+
if (/[A-Za-z_]/.test(character)) {
|
|
176
|
+
offset += 1;
|
|
177
|
+
while (offset < source.length && /[A-Za-z0-9_]/.test(source[offset])) {
|
|
178
|
+
offset += 1;
|
|
179
|
+
}
|
|
180
|
+
tokens.push(makeToken("identifier", source.slice(start, offset), start, offset, baseOffset));
|
|
181
|
+
continue;
|
|
182
|
+
}
|
|
183
|
+
const pair = source.slice(offset, offset + 2);
|
|
184
|
+
if (pair === "&&" || pair === "||") {
|
|
185
|
+
offset += 2;
|
|
186
|
+
tokens.push(makeToken(pair === "&&" ? "and" : "or", pair, start, offset, baseOffset));
|
|
187
|
+
continue;
|
|
188
|
+
}
|
|
189
|
+
offset += 1;
|
|
190
|
+
const kind = character === "!"
|
|
191
|
+
? "not"
|
|
192
|
+
: character === "("
|
|
193
|
+
? "open-parenthesis"
|
|
194
|
+
: character === ")"
|
|
195
|
+
? "close-parenthesis"
|
|
196
|
+
: "invalid";
|
|
197
|
+
tokens.push(makeToken(kind, character, start, offset, baseOffset));
|
|
198
|
+
}
|
|
199
|
+
tokens.push(makeToken("eof", "end of expression", source.length, source.length, baseOffset));
|
|
200
|
+
return tokens;
|
|
201
|
+
}
|
|
202
|
+
function makeToken(kind, text, start, end, baseOffset) {
|
|
203
|
+
return {
|
|
204
|
+
kind,
|
|
205
|
+
text,
|
|
206
|
+
range: { start: baseOffset + start, end: baseOffset + end },
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
function evaluateNode(expression, definitions) {
|
|
210
|
+
switch (expression.kind) {
|
|
211
|
+
case "identifier":
|
|
212
|
+
return definitions[expression.name] === true;
|
|
213
|
+
case "defined":
|
|
214
|
+
return Object.hasOwn(definitions, expression.name);
|
|
215
|
+
case "not":
|
|
216
|
+
return !evaluateNode(expression.operand, definitions);
|
|
217
|
+
case "logical": {
|
|
218
|
+
const left = evaluateNode(expression.left, definitions);
|
|
219
|
+
const right = evaluateNode(expression.right, definitions);
|
|
220
|
+
return expression.operator === "&&" ? left && right : left || right;
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
//# sourceMappingURL=expression.js.map
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// Copyright (C) 2026 Tencent. All rights reserved.
|
|
3
|
+
//
|
|
4
|
+
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
// you may not use this file except in compliance with the License.
|
|
6
|
+
// You may obtain a copy of the License at
|
|
7
|
+
//
|
|
8
|
+
// http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
//
|
|
10
|
+
// Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
// See the License for the specific language governing permissions and
|
|
14
|
+
// limitations under the License.
|
|
15
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
16
|
+
if (k2 === undefined) k2 = k;
|
|
17
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
18
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
19
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
20
|
+
}
|
|
21
|
+
Object.defineProperty(o, k2, desc);
|
|
22
|
+
}) : (function(o, m, k, k2) {
|
|
23
|
+
if (k2 === undefined) k2 = k;
|
|
24
|
+
o[k2] = m[k];
|
|
25
|
+
}));
|
|
26
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
27
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
28
|
+
};
|
|
29
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
30
|
+
exports.coreApiVersion = void 0;
|
|
31
|
+
/** Version of the public core API contract. */
|
|
32
|
+
exports.coreApiVersion = 1;
|
|
33
|
+
__exportStar(require("./conditional.js"), exports);
|
|
34
|
+
__exportStar(require("./expression.js"), exports);
|
|
35
|
+
__exportStar(require("./projection.js"), exports);
|
|
36
|
+
__exportStar(require("./scanner.js"), exports);
|
|
37
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { type ConditionalAnalysisResult } from "./conditional.js";
|
|
2
|
+
import type { MacroDefinitions } from "./expression.js";
|
|
3
|
+
import type { SourceRange } from "./scanner.js";
|
|
4
|
+
export interface ProjectionResult extends ConditionalAnalysisResult {
|
|
5
|
+
readonly projectedText: string;
|
|
6
|
+
readonly maskedRanges: readonly SourceRange[];
|
|
7
|
+
}
|
|
8
|
+
/** Analyze and project source through the selected macro definitions. */
|
|
9
|
+
export declare function projectSource(source: string, definitions: MacroDefinitions): ProjectionResult;
|
|
10
|
+
/**
|
|
11
|
+
* Replace non-newline text with whitespace while preserving both UTF-8 byte
|
|
12
|
+
* offsets and UTF-16 editor offsets.
|
|
13
|
+
*/
|
|
14
|
+
export declare function maskSourceRanges(source: string, ranges: readonly SourceRange[]): string;
|
|
15
|
+
//# sourceMappingURL=projection.d.ts.map
|