@tolgee/cli 2.0.4 → 2.1.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/cli.js +15 -23
- package/dist/commands/extract/check.js +2 -6
- package/dist/commands/extract/print.js +2 -6
- package/dist/commands/sync/compare.js +2 -6
- package/dist/commands/sync/sync.js +1 -5
- package/dist/commands/tag.js +1 -5
- package/dist/config/tolgeerc.js +4 -6
- package/dist/extractor/extractor.js +40 -66
- package/dist/extractor/parser/extractComment.js +78 -0
- package/dist/extractor/parser/generalMapper.js +82 -0
- package/dist/extractor/parser/generateReport.js +161 -0
- package/dist/extractor/parser/iterator.js +37 -0
- package/dist/extractor/parser/mergerMachine.js +66 -0
- package/dist/extractor/parser/nodeUtils.js +21 -0
- package/dist/extractor/parser/parser.js +129 -0
- package/dist/extractor/parser/rules/tComponentGeneral.js +45 -0
- package/dist/extractor/parser/rules/tFunctionGeneral.js +41 -0
- package/dist/extractor/parser/rules/tNsSourceGeneral.js +36 -0
- package/dist/extractor/parser/tokenMergers/closingTagMerger.js +27 -0
- package/dist/extractor/parser/tokenMergers/commentsMerger.js +36 -0
- package/dist/extractor/parser/tokenMergers/stringMerger.js +50 -0
- package/dist/extractor/parser/tokenMergers/templateStringMerger.js +55 -0
- package/dist/extractor/parser/tokenMergers/typesAsMergerer.js +40 -0
- package/dist/extractor/parser/tokenMergers/typesCastMerger.js +28 -0
- package/dist/extractor/parser/tree/getTranslateProps.js +54 -0
- package/dist/extractor/parser/tree/getValue.js +17 -0
- package/dist/extractor/parser/tree/parseGeneral.js +51 -0
- package/dist/extractor/parser/tree/parseList.js +33 -0
- package/dist/extractor/parser/tree/parseObject.js +130 -0
- package/dist/extractor/parser/tree/parseProps.js +56 -0
- package/dist/extractor/parser/tree/parseTag.js +26 -0
- package/dist/extractor/parser/types.js +1 -0
- package/dist/extractor/parserReact/ParserReact.js +30 -0
- package/dist/extractor/parserReact/jsxMapper.js +24 -0
- package/dist/extractor/parserReact/rules/createElement.js +62 -0
- package/dist/extractor/parserReact/rules/tComponent.js +9 -0
- package/dist/extractor/parserReact/rules/tFunction.js +7 -0
- package/dist/extractor/parserReact/rules/useTranslate.js +7 -0
- package/dist/extractor/parserReact/tokenMergers/createElementMerger.js +35 -0
- package/dist/extractor/parserReact/tokenMergers/tComponentMerger.js +20 -0
- package/dist/extractor/parserReact/tokenMergers/tFunctionMerger.js +23 -0
- package/dist/extractor/parserReact/tokenMergers/useTranslateMerger.js +20 -0
- package/dist/extractor/parserSvelte/ParserSvelte.js +32 -0
- package/dist/extractor/parserSvelte/contextConstants.js +1 -0
- package/dist/extractor/parserSvelte/rules/scriptTag.js +26 -0
- package/dist/extractor/parserSvelte/rules/tComponent.js +9 -0
- package/dist/extractor/parserSvelte/rules/tFunction.js +7 -0
- package/dist/extractor/parserSvelte/rules/useTranslate.js +7 -0
- package/dist/extractor/parserSvelte/svelteMapper.js +39 -0
- package/dist/extractor/parserSvelte/svelteTreeTransform.js +38 -0
- package/dist/extractor/parserSvelte/tokenMergers/getTranslateMerger.js +20 -0
- package/dist/extractor/parserSvelte/tokenMergers/scriptTagMerger.js +20 -0
- package/dist/extractor/parserSvelte/tokenMergers/tComponentMerger.js +20 -0
- package/dist/extractor/parserSvelte/tokenMergers/tFunctionMerger.js +29 -0
- package/dist/extractor/parserVue/ParserVue.js +45 -0
- package/dist/extractor/parserVue/contextConstants.js +3 -0
- package/dist/extractor/parserVue/rules/exportDefaultObject.js +14 -0
- package/dist/extractor/parserVue/rules/globalTFunction.js +7 -0
- package/dist/extractor/parserVue/rules/scriptTag.js +31 -0
- package/dist/extractor/parserVue/rules/tComponent.js +9 -0
- package/dist/extractor/parserVue/rules/tFunction.js +7 -0
- package/dist/extractor/parserVue/rules/useTranslate.js +7 -0
- package/dist/extractor/parserVue/tokenMergers/exportDefaultObjectMerger.js +25 -0
- package/dist/extractor/parserVue/tokenMergers/globalTFunctionMerger.js +36 -0
- package/dist/extractor/parserVue/tokenMergers/scriptTagMerger.js +20 -0
- package/dist/extractor/parserVue/tokenMergers/tComponentMerger.js +20 -0
- package/dist/extractor/parserVue/tokenMergers/tFunctionMerger.js +37 -0
- package/dist/extractor/parserVue/tokenMergers/useTranslateMerger.js +20 -0
- package/dist/extractor/parserVue/vueMapper.js +51 -0
- package/dist/extractor/parserVue/vueTreeTransform.js +109 -0
- package/dist/extractor/runner.js +52 -4
- package/dist/extractor/visualizers/printTokens.js +7 -0
- package/dist/extractor/visualizers/tokensToString.js +28 -0
- package/dist/extractor/visualizers/visualizeRules.js +40 -0
- package/dist/extractor/warnings.js +4 -0
- package/dist/extractor/worker.js +10 -7
- package/dist/options.js +5 -0
- package/extractor.d.ts +8 -1
- package/package.json +2 -4
- package/schema.json +18 -6
- package/dist/client/internal/requester.js +0 -130
- package/dist/extractor/machines/comments.js +0 -78
- package/dist/extractor/machines/react.js +0 -705
- package/dist/extractor/machines/shared/comments.js +0 -79
- package/dist/extractor/machines/shared/properties.js +0 -380
- package/dist/extractor/machines/shared/translateCall.js +0 -141
- package/dist/extractor/machines/svelte.js +0 -429
- package/dist/extractor/machines/vue/decoder.js +0 -194
- package/dist/extractor/machines/vue/extract.js +0 -491
- package/dist/extractor/processors/vueSfc.js +0 -55
package/dist/cli.js
CHANGED
@@ -4,7 +4,7 @@ import ansi from 'ansi-colors';
|
|
4
4
|
import { getApiKey, savePak, savePat } from './config/credentials.js';
|
5
5
|
import loadTolgeeRc from './config/tolgeerc.js';
|
6
6
|
import { setDebug, info, error, exitWithError } from './utils/logger.js';
|
7
|
-
import { API_KEY_OPT, API_URL_OPT, CONFIG_OPT, EXTRACTOR, FILE_PATTERNS, FORMAT_OPT, PROJECT_ID_OPT, } from './options.js';
|
7
|
+
import { API_KEY_OPT, API_URL_OPT, CONFIG_OPT, DEFAULT_NAMESPACE, EXTRACTOR, FILE_PATTERNS, FORMAT_OPT, STRICT_NAMESPACE, PARSER, PROJECT_ID_OPT, STRICT_NAMESPACE_NEGATION, VERBOSE, } from './options.js';
|
8
8
|
import { API_KEY_PAK_PREFIX, API_KEY_PAT_PREFIX, DEFAULT_API_URL, VERSION, } from './constants.js';
|
9
9
|
import { Login, Logout } from './commands/login.js';
|
10
10
|
import PushCommand from './commands/push.js';
|
@@ -87,43 +87,35 @@ const preHandler = (config) => async function (prog, cmd) {
|
|
87
87
|
cmd.setOptionValue('client', client);
|
88
88
|
}
|
89
89
|
// Apply verbosity
|
90
|
-
setDebug(prog.opts().verbose);
|
90
|
+
setDebug(Boolean(prog.opts().verbose));
|
91
91
|
};
|
92
92
|
const program = new Command('tolgee')
|
93
93
|
.version(VERSION)
|
94
94
|
.configureOutput({ writeErr: error })
|
95
|
-
.description('Command Line Interface to interact with the Tolgee Platform')
|
96
|
-
.option('-v, --verbose', 'Enable verbose logging.');
|
95
|
+
.description('Command Line Interface to interact with the Tolgee Platform');
|
97
96
|
// get config path to update defaults
|
98
97
|
const configPath = getSingleOption(CONFIG_OPT, process.argv);
|
99
98
|
async function loadConfig(program) {
|
100
99
|
const tgConfig = await loadTolgeeRc(configPath);
|
101
|
-
if (tgConfig) {
|
102
|
-
[program, ...program.commands].forEach((cmd) => cmd.options.forEach((opt) => {
|
103
|
-
const key = opt.attributeName();
|
104
|
-
const value = tgConfig[key];
|
105
|
-
if (value) {
|
106
|
-
const parsedValue = opt.parseArg
|
107
|
-
? opt.parseArg(value, undefined)
|
108
|
-
: value;
|
109
|
-
cmd.setOptionValueWithSource(key, parsedValue, 'config');
|
110
|
-
}
|
111
|
-
}));
|
112
|
-
}
|
113
100
|
return tgConfig ?? {};
|
114
101
|
}
|
115
102
|
async function run() {
|
116
103
|
try {
|
104
|
+
const config = await loadConfig(program);
|
105
|
+
program.hook('preAction', preHandler(config));
|
117
106
|
// Global options
|
107
|
+
program.addOption(VERBOSE);
|
118
108
|
program.addOption(CONFIG_OPT);
|
119
|
-
program.addOption(API_URL_OPT.default(DEFAULT_API_URL));
|
109
|
+
program.addOption(API_URL_OPT.default(config.apiUrl ?? DEFAULT_API_URL));
|
120
110
|
program.addOption(API_KEY_OPT);
|
121
|
-
program.addOption(PROJECT_ID_OPT.default(-1));
|
122
|
-
program.addOption(FORMAT_OPT.default('JSON_TOLGEE'));
|
123
|
-
program.addOption(EXTRACTOR);
|
124
|
-
program.addOption(FILE_PATTERNS);
|
125
|
-
|
126
|
-
program.
|
111
|
+
program.addOption(PROJECT_ID_OPT.default(config.projectId ?? -1));
|
112
|
+
program.addOption(FORMAT_OPT.default(config.format ?? 'JSON_TOLGEE'));
|
113
|
+
program.addOption(EXTRACTOR.default(config.extractor));
|
114
|
+
program.addOption(FILE_PATTERNS.default(config.patterns));
|
115
|
+
program.addOption(PARSER.default(config.parser));
|
116
|
+
program.addOption(STRICT_NAMESPACE.default(config.strictNamespace ?? true));
|
117
|
+
program.addOption(STRICT_NAMESPACE_NEGATION);
|
118
|
+
program.addOption(DEFAULT_NAMESPACE.default(config.defaultNamespace));
|
127
119
|
// Register commands
|
128
120
|
program.addCommand(Login);
|
129
121
|
program.addCommand(Logout);
|
@@ -2,14 +2,10 @@ import { relative } from 'path';
|
|
2
2
|
import { Command } from 'commander';
|
3
3
|
import { extractKeysOfFiles } from '../../extractor/runner.js';
|
4
4
|
import { WarningMessages, emitGitHubWarning, } from '../../extractor/warnings.js';
|
5
|
-
import {
|
5
|
+
import { loading } from '../../utils/logger.js';
|
6
6
|
const lintHandler = (config) => async function () {
|
7
7
|
const opts = this.optsWithGlobals();
|
8
|
-
const
|
9
|
-
if (!patterns?.length) {
|
10
|
-
exitWithError('Missing option --patterns or config.patterns option');
|
11
|
-
}
|
12
|
-
const extracted = await loading('Analyzing code...', extractKeysOfFiles(patterns, opts.extractor));
|
8
|
+
const extracted = await loading('Analyzing code...', extractKeysOfFiles(opts));
|
13
9
|
let warningCount = 0;
|
14
10
|
let filesCount = 0;
|
15
11
|
for (const [file, { warnings }] of extracted) {
|
@@ -2,14 +2,10 @@ import { relative } from 'path';
|
|
2
2
|
import { Command } from 'commander';
|
3
3
|
import { extractKeysOfFiles } from '../../extractor/runner.js';
|
4
4
|
import { WarningMessages } from '../../extractor/warnings.js';
|
5
|
-
import {
|
5
|
+
import { loading } from '../../utils/logger.js';
|
6
6
|
const printHandler = (config) => async function () {
|
7
7
|
const opts = this.optsWithGlobals();
|
8
|
-
const
|
9
|
-
if (!patterns?.length) {
|
10
|
-
exitWithError('Missing option --patterns or config.patterns option');
|
11
|
-
}
|
12
|
-
const extracted = await loading('Analyzing code...', extractKeysOfFiles(patterns, opts.extractor));
|
8
|
+
const extracted = await loading('Analyzing code...', extractKeysOfFiles(opts));
|
13
9
|
let warningCount = 0;
|
14
10
|
const keySet = new Set();
|
15
11
|
for (const [file, { keys, warnings }] of extracted) {
|
@@ -3,15 +3,11 @@ import ansi from 'ansi-colors';
|
|
3
3
|
import { compareKeys, printKey } from './syncUtils.js';
|
4
4
|
import { extractKeysOfFiles, filterExtractionResult, } from '../../extractor/runner.js';
|
5
5
|
import { dumpWarnings } from '../../extractor/warnings.js';
|
6
|
-
import {
|
6
|
+
import { loading } from '../../utils/logger.js';
|
7
7
|
import { handleLoadableError } from '../../client/TolgeeClient.js';
|
8
8
|
const asyncHandler = (config) => async function () {
|
9
9
|
const opts = this.optsWithGlobals();
|
10
|
-
const
|
11
|
-
if (!patterns?.length) {
|
12
|
-
exitWithError('Missing argument <patterns>');
|
13
|
-
}
|
14
|
-
const rawKeys = await loading('Analyzing code...', extractKeysOfFiles(patterns, opts.extractor));
|
10
|
+
const rawKeys = await loading('Analyzing code...', extractKeysOfFiles(opts));
|
15
11
|
dumpWarnings(rawKeys);
|
16
12
|
const localKeys = filterExtractionResult(rawKeys);
|
17
13
|
const loadable = await opts.client.GET('/v2/projects/{projectId}/all-keys', { params: { path: { projectId: opts.client.getProjectId() } } });
|
@@ -33,11 +33,7 @@ async function askForConfirmation(keys, operation) {
|
|
33
33
|
}
|
34
34
|
const syncHandler = (config) => async function () {
|
35
35
|
const opts = this.optsWithGlobals();
|
36
|
-
const
|
37
|
-
if (!patterns?.length) {
|
38
|
-
exitWithError('Missing argument <patterns>');
|
39
|
-
}
|
40
|
-
const rawKeys = await loading('Analyzing code...', extractKeysOfFiles(patterns, opts.extractor));
|
36
|
+
const rawKeys = await loading('Analyzing code...', extractKeysOfFiles(opts));
|
41
37
|
const warnCount = dumpWarnings(rawKeys);
|
42
38
|
if (!opts.continueOnWarning && warnCount) {
|
43
39
|
console.log(ansi.bold.red('Aborting as warnings have been emitted.'));
|
package/dist/commands/tag.js
CHANGED
@@ -9,11 +9,7 @@ const tagHandler = (config) => async function () {
|
|
9
9
|
if (opts.filterExtracted && opts.filterNotExtracted) {
|
10
10
|
exitWithError('Use either "--filter-extracted" or "--filter-not-extracted", not both');
|
11
11
|
}
|
12
|
-
const
|
13
|
-
if (!patterns?.length) {
|
14
|
-
exitWithError('Missing option --patterns or config.patterns option');
|
15
|
-
}
|
16
|
-
const extracted = await loading('Analyzing code...', extractKeysOfFiles(patterns, opts.extractor));
|
12
|
+
const extracted = await loading('Analyzing code...', extractKeysOfFiles(opts));
|
17
13
|
const keys = [...extracted.values()].flatMap((item) => item.keys);
|
18
14
|
extractedKeys = keys.map((key) => ({
|
19
15
|
name: key.keyName,
|
package/dist/config/tolgeerc.js
CHANGED
@@ -26,17 +26,18 @@ function parseConfig(input, configDir) {
|
|
26
26
|
throw new Error('Invalid config: projectId should be an integer representing your project Id');
|
27
27
|
}
|
28
28
|
}
|
29
|
+
// convert relative paths in config to absolute
|
29
30
|
if (rc.extractor !== undefined) {
|
30
31
|
rc.extractor = resolve(configDir, rc.extractor);
|
31
32
|
if (!existsSync(rc.extractor)) {
|
32
33
|
throw new Error(`Invalid config: extractor points to a file that does not exists (${rc.extractor})`);
|
33
34
|
}
|
34
35
|
}
|
35
|
-
|
36
|
-
|
36
|
+
// convert relative paths in config to absolute
|
37
|
+
if (rc.patterns !== undefined) {
|
38
|
+
rc.patterns = rc.patterns.map((pattern) => resolve(configDir, pattern));
|
37
39
|
}
|
38
40
|
// convert relative paths in config to absolute
|
39
|
-
// so it's always relative to config location
|
40
41
|
if (rc.push?.files) {
|
41
42
|
rc.push.files = rc.push.files.map((r) => ({
|
42
43
|
...r,
|
@@ -46,9 +47,6 @@ function parseConfig(input, configDir) {
|
|
46
47
|
if (rc.pull?.path !== undefined) {
|
47
48
|
rc.pull.path = resolve(configDir, rc.pull.path);
|
48
49
|
}
|
49
|
-
if (rc.patterns !== undefined) {
|
50
|
-
rc.patterns = rc.patterns.map((pattern) => resolve(configDir, pattern));
|
51
|
-
}
|
52
50
|
return rc;
|
53
51
|
}
|
54
52
|
async function getSchema() {
|
@@ -1,72 +1,46 @@
|
|
1
|
-
import { extname } from 'path';
|
2
|
-
import { interpret } from 'xstate';
|
3
|
-
import reactExtractorMachine from './machines/react.js';
|
4
|
-
import vueExtractorMachine from './machines/vue/extract.js';
|
5
|
-
import svelteExtractorMachine from './machines/svelte.js';
|
6
|
-
import commentsExtractorMachine from './machines/comments.js';
|
7
|
-
import vueSfcProcessor from './processors/vueSfc.js';
|
8
1
|
import tokenizer from './tokenizer.js';
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
'.mjs',
|
23
|
-
'.cjs',
|
24
|
-
'.ts',
|
25
|
-
'.mts',
|
26
|
-
'.cts',
|
27
|
-
'.jsx',
|
28
|
-
'.tsx',
|
29
|
-
'.svelte',
|
30
|
-
];
|
31
|
-
function pickMachine(code, ext) {
|
32
|
-
if (REACT_EXTS.includes(ext) && code.includes('@tolgee/react')) {
|
33
|
-
return reactExtractorMachine;
|
2
|
+
import { ParserReact } from './parserReact/ParserReact.js';
|
3
|
+
import { tokensList } from './visualizers/printTokens.js';
|
4
|
+
import { visualizeRules } from './visualizers/visualizeRules.js';
|
5
|
+
import { ParserVue } from './parserVue/ParserVue.js';
|
6
|
+
import { ParserSvelte } from './parserSvelte/ParserSvelte.js';
|
7
|
+
function pickParser(format) {
|
8
|
+
switch (format) {
|
9
|
+
case 'react':
|
10
|
+
return ParserReact();
|
11
|
+
case 'vue':
|
12
|
+
return ParserVue();
|
13
|
+
case 'svelte':
|
14
|
+
return ParserSvelte();
|
34
15
|
}
|
35
|
-
if (VUE_EXTS.includes(ext) && code.includes('@tolgee/vue')) {
|
36
|
-
return vueExtractorMachine;
|
37
|
-
}
|
38
|
-
if (ext === '.svelte' && code.includes('@tolgee/svelte')) {
|
39
|
-
return svelteExtractorMachine;
|
40
|
-
}
|
41
|
-
if (ALL_EXTS.includes(ext) &&
|
42
|
-
(code.includes('@tolgee-key') || code.includes('@tolgee-ignore'))) {
|
43
|
-
return commentsExtractorMachine;
|
44
|
-
}
|
45
|
-
return null;
|
46
16
|
}
|
47
|
-
export
|
48
|
-
const
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
17
|
+
export async function extractTreeAndReport(code, fileName, parserType, options) {
|
18
|
+
const debug = options.verbose?.includes('extractor');
|
19
|
+
const tokens = (await tokenizer(code, fileName));
|
20
|
+
const parser = pickParser(parserType);
|
21
|
+
const tokensMerged = [];
|
22
|
+
const tokensWithRules = [];
|
23
|
+
let onAccept = undefined;
|
24
|
+
if (debug) {
|
25
|
+
onAccept = (token, type) => {
|
26
|
+
tokensMerged.push(token);
|
27
|
+
tokensWithRules.push({ ...token, customType: type });
|
28
|
+
};
|
55
29
|
}
|
56
|
-
const
|
57
|
-
|
58
|
-
|
30
|
+
const result = parser.parse({
|
31
|
+
tokens,
|
32
|
+
onAccept,
|
33
|
+
options,
|
34
|
+
});
|
35
|
+
if (debug) {
|
36
|
+
console.log(JSON.stringify(result.tree, null, 2));
|
37
|
+
console.log(tokensList(tokensMerged));
|
38
|
+
console.log(visualizeRules(tokensMerged, code));
|
39
|
+
console.log(visualizeRules(tokensWithRules, code));
|
59
40
|
}
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
machine.send(token);
|
66
|
-
}
|
67
|
-
const snapshot = machine.getSnapshot();
|
68
|
-
return {
|
69
|
-
warnings: snapshot.context.warnings,
|
70
|
-
keys: snapshot.context.keys,
|
71
|
-
};
|
41
|
+
return result;
|
42
|
+
}
|
43
|
+
export default async function extractor(code, fileName, parserType, options) {
|
44
|
+
const result = await extractTreeAndReport(code, fileName, parserType, options);
|
45
|
+
return result.report;
|
72
46
|
}
|
@@ -0,0 +1,78 @@
|
|
1
|
+
import JSON5 from 'json5';
|
2
|
+
function isValidKeyOverride(data) {
|
3
|
+
if (!('key' in data)) {
|
4
|
+
return false;
|
5
|
+
}
|
6
|
+
if (typeof data.key !== 'string') {
|
7
|
+
return false;
|
8
|
+
}
|
9
|
+
if ('ns' in data && typeof data.ns !== 'string') {
|
10
|
+
return false;
|
11
|
+
}
|
12
|
+
if ('defaultValue' in data && typeof data.defaultValue !== 'string') {
|
13
|
+
return false;
|
14
|
+
}
|
15
|
+
return true;
|
16
|
+
}
|
17
|
+
function getEndLine(token) {
|
18
|
+
return token.line + (token.token.match(/\n/gm)?.length ?? 0);
|
19
|
+
}
|
20
|
+
export function extractComment(token) {
|
21
|
+
const comment = token.token.trim();
|
22
|
+
if (comment.startsWith('@tolgee-ignore')) {
|
23
|
+
return {
|
24
|
+
type: 'MAGIC_COMMENT',
|
25
|
+
kind: 'ignore',
|
26
|
+
line: getEndLine(token),
|
27
|
+
};
|
28
|
+
}
|
29
|
+
if (comment.startsWith('@tolgee-key')) {
|
30
|
+
const data = comment.slice(11).trim();
|
31
|
+
// Data is escaped; extract all as string
|
32
|
+
if (data.startsWith('\\')) {
|
33
|
+
return {
|
34
|
+
type: 'MAGIC_COMMENT',
|
35
|
+
kind: 'key',
|
36
|
+
keyName: data.slice(1),
|
37
|
+
line: getEndLine(token),
|
38
|
+
};
|
39
|
+
}
|
40
|
+
// Data is a json5 struct
|
41
|
+
if (data.startsWith('{')) {
|
42
|
+
try {
|
43
|
+
const key = JSON5.parse(data);
|
44
|
+
if (!isValidKeyOverride(key)) {
|
45
|
+
// No key in the struct; invalid override
|
46
|
+
return {
|
47
|
+
type: 'WARNING',
|
48
|
+
kind: 'W_INVALID_KEY_OVERRIDE',
|
49
|
+
line: getEndLine(token),
|
50
|
+
};
|
51
|
+
}
|
52
|
+
else {
|
53
|
+
return {
|
54
|
+
type: 'MAGIC_COMMENT',
|
55
|
+
kind: 'key',
|
56
|
+
keyName: key.key,
|
57
|
+
namespace: key.ns,
|
58
|
+
defaultValue: key.defaultValue,
|
59
|
+
line: getEndLine(token),
|
60
|
+
};
|
61
|
+
}
|
62
|
+
}
|
63
|
+
catch {
|
64
|
+
return {
|
65
|
+
type: 'WARNING',
|
66
|
+
kind: 'W_MALFORMED_KEY_OVERRIDE',
|
67
|
+
line: getEndLine(token),
|
68
|
+
};
|
69
|
+
}
|
70
|
+
}
|
71
|
+
return {
|
72
|
+
type: 'MAGIC_COMMENT',
|
73
|
+
kind: 'key',
|
74
|
+
keyName: data,
|
75
|
+
line: getEndLine(token),
|
76
|
+
};
|
77
|
+
}
|
78
|
+
}
|
@@ -0,0 +1,82 @@
|
|
1
|
+
export const generalMapper = (token) => {
|
2
|
+
switch (token.type) {
|
3
|
+
// comments
|
4
|
+
case 'punctuation.definition.comment.ts':
|
5
|
+
return 'comment.definition';
|
6
|
+
case 'comment.block.ts':
|
7
|
+
case 'comment.block.documentation.ts':
|
8
|
+
return 'comment.block';
|
9
|
+
case 'comment.line.double-slash.ts':
|
10
|
+
return 'comment.line';
|
11
|
+
// primitives
|
12
|
+
case 'constant.language.null.ts':
|
13
|
+
return 'primitive.null';
|
14
|
+
// plain strings
|
15
|
+
case 'punctuation.definition.string.begin.ts':
|
16
|
+
return 'string.begin';
|
17
|
+
case 'punctuation.definition.string.end.ts':
|
18
|
+
return 'string.end';
|
19
|
+
case 'string.quoted.single.ts':
|
20
|
+
case 'string.quoted.double.ts':
|
21
|
+
return 'string.body';
|
22
|
+
// template strings
|
23
|
+
case 'punctuation.definition.string.template.begin.ts':
|
24
|
+
return 'string.teplate.begin';
|
25
|
+
case 'punctuation.definition.string.template.end.ts':
|
26
|
+
return 'string.template.end';
|
27
|
+
case 'string.template.ts':
|
28
|
+
return 'string.template.body';
|
29
|
+
// variables
|
30
|
+
case 'variable.other.object.ts':
|
31
|
+
case 'variable.other.constant.ts':
|
32
|
+
case 'variable.language.this.ts':
|
33
|
+
return 'variable';
|
34
|
+
// function
|
35
|
+
case 'support.function.dom.ts':
|
36
|
+
case 'entity.name.function.ts':
|
37
|
+
return 'function.call';
|
38
|
+
// "async" word
|
39
|
+
case 'storage.modifier.async.ts':
|
40
|
+
return 'kw.async';
|
41
|
+
// "." accessor
|
42
|
+
case 'punctuation.accessor.ts':
|
43
|
+
return 'acessor.dot';
|
44
|
+
// ":" accessor
|
45
|
+
case 'punctuation.separator.label.ts':
|
46
|
+
case 'punctuation.separator.key-value.ts':
|
47
|
+
return 'acessor.doublecolon';
|
48
|
+
// "," separator
|
49
|
+
case 'punctuation.separator.comma.ts':
|
50
|
+
return 'separator.comma';
|
51
|
+
// "="
|
52
|
+
case 'keyword.operator.assignment.ts':
|
53
|
+
return 'operator.assignment';
|
54
|
+
// curly brackets - blocks
|
55
|
+
case 'punctuation.definition.block.ts':
|
56
|
+
return token.token === '{' ? 'block.begin' : 'block.end';
|
57
|
+
// rounded brackets - expressions
|
58
|
+
case 'punctuation.definition.parameters.begin.ts':
|
59
|
+
case 'punctuation.definition.parameters.end.ts':
|
60
|
+
case 'meta.brace.round.ts':
|
61
|
+
return token.token === '(' ? 'expression.begin' : 'expression.end';
|
62
|
+
case 'meta.brace.square.ts':
|
63
|
+
return token.token === '[' ? 'list.begin' : 'list.end';
|
64
|
+
case 'meta.object-literal.key.ts':
|
65
|
+
case 'entity.name.label.ts':
|
66
|
+
return 'object.key';
|
67
|
+
case 'variable.other.readwrite.ts':
|
68
|
+
return 'variable';
|
69
|
+
// ignore type annotations
|
70
|
+
case 'keyword.control.as.ts':
|
71
|
+
return 'typescript.as';
|
72
|
+
case 'support.type.primitive.ts':
|
73
|
+
case 'entity.name.type.ts':
|
74
|
+
return 'typescript.type.primitive';
|
75
|
+
case 'meta.brace.angle.ts':
|
76
|
+
return token.token === '<'
|
77
|
+
? 'typescript.expr.begin'
|
78
|
+
: 'typescript.expr.end';
|
79
|
+
case 'keyword.operator.type.ts':
|
80
|
+
return 'typescript.operator';
|
81
|
+
}
|
82
|
+
};
|
@@ -0,0 +1,161 @@
|
|
1
|
+
import { extractString, isString } from './nodeUtils.js';
|
2
|
+
function shouldBeIgnored(context, line) {
|
3
|
+
const commentAtLine = context.commentMap.get(line - 1);
|
4
|
+
const isIgnore = commentAtLine?.type === 'MAGIC_COMMENT' && commentAtLine.kind === 'ignore';
|
5
|
+
if (isIgnore) {
|
6
|
+
context.unusedComments.delete(commentAtLine);
|
7
|
+
}
|
8
|
+
return isIgnore;
|
9
|
+
}
|
10
|
+
function keyInfoFromComment(context, line) {
|
11
|
+
const commentAtLine = context.commentMap.get(line - 1);
|
12
|
+
const isKeyInfo = commentAtLine?.type === 'MAGIC_COMMENT' && commentAtLine.kind === 'key';
|
13
|
+
if (isKeyInfo) {
|
14
|
+
context.unusedComments.delete(commentAtLine);
|
15
|
+
return {
|
16
|
+
keyName: commentAtLine.keyName,
|
17
|
+
namespace: commentAtLine.namespace,
|
18
|
+
defaultValue: commentAtLine.defaultValue,
|
19
|
+
line: commentAtLine.line,
|
20
|
+
};
|
21
|
+
}
|
22
|
+
return undefined;
|
23
|
+
}
|
24
|
+
function reportKey(context, node, contextNs) {
|
25
|
+
const { strictNamespace, defaultNamespace } = context.options;
|
26
|
+
const { keys, warnings } = context;
|
27
|
+
const { keyName, namespace: keyNs, defaultValue, line, dependsOnContext, optionsDynamic, } = node;
|
28
|
+
if (shouldBeIgnored(context, line)) {
|
29
|
+
return { keys, warnings };
|
30
|
+
}
|
31
|
+
const overrideInfo = keyInfoFromComment(context, node.line);
|
32
|
+
if (overrideInfo) {
|
33
|
+
// key info is overriten by comment
|
34
|
+
keys.push(overrideInfo);
|
35
|
+
return;
|
36
|
+
}
|
37
|
+
if (!keyName || !isString(keyName)) {
|
38
|
+
// dynamic key or key not present
|
39
|
+
warnings.push({ line, warning: 'W_DYNAMIC_KEY' });
|
40
|
+
return;
|
41
|
+
}
|
42
|
+
if (dependsOnContext && !contextNs && !keyNs && strictNamespace) {
|
43
|
+
// there is no namespace source so namespace is ambiguous
|
44
|
+
warnings.push({ line, warning: 'W_MISSING_T_SOURCE' });
|
45
|
+
return;
|
46
|
+
}
|
47
|
+
if (optionsDynamic && strictNamespace) {
|
48
|
+
// options of the key can't be analyzed, so again ambiguous namespace
|
49
|
+
warnings.push({ line, warning: 'W_DYNAMIC_OPTIONS' });
|
50
|
+
return;
|
51
|
+
}
|
52
|
+
const namespace = keyNs ?? (dependsOnContext ? contextNs?.name : undefined);
|
53
|
+
if (namespace && !isString(namespace)) {
|
54
|
+
// namespace is dynamic
|
55
|
+
if (namespace === contextNs?.name) {
|
56
|
+
// namespace coming from context
|
57
|
+
warnings.push({ line, warning: 'W_UNRESOLVABLE_NAMESPACE' });
|
58
|
+
}
|
59
|
+
else {
|
60
|
+
// namespace is directly on key
|
61
|
+
warnings.push({
|
62
|
+
line,
|
63
|
+
warning: 'W_DYNAMIC_NAMESPACE',
|
64
|
+
});
|
65
|
+
}
|
66
|
+
return;
|
67
|
+
}
|
68
|
+
if (defaultValue !== undefined && !isString(defaultValue)) {
|
69
|
+
// this is just warning, we can still extract
|
70
|
+
warnings.push({ line, warning: 'W_DYNAMIC_DEFAULT_VALUE' });
|
71
|
+
}
|
72
|
+
keys.push({
|
73
|
+
line,
|
74
|
+
keyName: extractString(keyName),
|
75
|
+
namespace: extractString(namespace) ?? defaultNamespace,
|
76
|
+
defaultValue: extractString(defaultValue),
|
77
|
+
});
|
78
|
+
}
|
79
|
+
function reportNs(context, node) {
|
80
|
+
const { warnings } = context;
|
81
|
+
const { line, name } = node;
|
82
|
+
if (name && !isString(name)) {
|
83
|
+
warnings.push({ line, warning: 'W_DYNAMIC_NAMESPACE' });
|
84
|
+
}
|
85
|
+
}
|
86
|
+
function reportGeneral(context, node, contextNs) {
|
87
|
+
if (!node) {
|
88
|
+
return;
|
89
|
+
}
|
90
|
+
if (node.type === 'expr' || node.type === 'array') {
|
91
|
+
let namespace = contextNs;
|
92
|
+
for (const item of node.values) {
|
93
|
+
if (item.type === 'nsInfo') {
|
94
|
+
const oldNamespace = namespace;
|
95
|
+
if (!shouldBeIgnored(context, item.line)) {
|
96
|
+
reportNs(context, item);
|
97
|
+
namespace = item;
|
98
|
+
}
|
99
|
+
// there might be nested stuff
|
100
|
+
reportGeneral(context, item.name, oldNamespace);
|
101
|
+
for (const i of item.values) {
|
102
|
+
reportGeneral(context, i, oldNamespace);
|
103
|
+
}
|
104
|
+
}
|
105
|
+
else {
|
106
|
+
reportGeneral(context, item, namespace);
|
107
|
+
}
|
108
|
+
}
|
109
|
+
}
|
110
|
+
else if (node.type === 'keyInfo') {
|
111
|
+
reportKey(context, node, contextNs);
|
112
|
+
// there might be nested stuff
|
113
|
+
reportGeneral(context, node.keyName, contextNs);
|
114
|
+
reportGeneral(context, node.namespace, contextNs);
|
115
|
+
reportGeneral(context, node.defaultValue, contextNs);
|
116
|
+
for (const i of node.values) {
|
117
|
+
reportGeneral(context, i, contextNs);
|
118
|
+
}
|
119
|
+
}
|
120
|
+
else if (node.type === 'dict') {
|
121
|
+
for (const item of Object.values(node.value)) {
|
122
|
+
reportGeneral(context, item, contextNs);
|
123
|
+
}
|
124
|
+
// go through values with unknown keynames
|
125
|
+
for (const item of node.unknown) {
|
126
|
+
reportGeneral(context, item, contextNs);
|
127
|
+
}
|
128
|
+
}
|
129
|
+
}
|
130
|
+
export function generateReport({ node, contextNs, comments, options, }) {
|
131
|
+
const commentMap = new Map();
|
132
|
+
comments.forEach((item) => {
|
133
|
+
commentMap.set(item.line, item);
|
134
|
+
});
|
135
|
+
const unusedComments = new Set(comments);
|
136
|
+
const context = {
|
137
|
+
commentMap,
|
138
|
+
unusedComments,
|
139
|
+
options,
|
140
|
+
keys: [],
|
141
|
+
warnings: [],
|
142
|
+
};
|
143
|
+
reportGeneral(context, node, contextNs);
|
144
|
+
unusedComments.forEach((value) => {
|
145
|
+
if (value.type === 'WARNING') {
|
146
|
+
context.warnings.push({ line: value.line, warning: value.kind });
|
147
|
+
}
|
148
|
+
else if (value.type === 'MAGIC_COMMENT' && value.kind === 'key') {
|
149
|
+
context.keys.push({
|
150
|
+
keyName: value.keyName,
|
151
|
+
namespace: value.namespace,
|
152
|
+
defaultValue: value.defaultValue,
|
153
|
+
line: value.line,
|
154
|
+
});
|
155
|
+
}
|
156
|
+
else if (value.type === 'MAGIC_COMMENT' && value.kind === 'ignore') {
|
157
|
+
context.warnings.push({ line: value.line, warning: 'W_UNUSED_IGNORE' });
|
158
|
+
}
|
159
|
+
});
|
160
|
+
return { keys: context.keys, warnings: context.warnings };
|
161
|
+
}
|
@@ -0,0 +1,37 @@
|
|
1
|
+
export const createIterator = (items) => {
|
2
|
+
const iterator = items[Symbol.iterator]();
|
3
|
+
let currentItem;
|
4
|
+
let nextItem = iterator.next();
|
5
|
+
const listeners = [];
|
6
|
+
let currentContext = undefined;
|
7
|
+
const self = {
|
8
|
+
getLineNumber() {
|
9
|
+
return currentItem?.line ?? 0;
|
10
|
+
},
|
11
|
+
current() {
|
12
|
+
return currentItem;
|
13
|
+
},
|
14
|
+
peek() {
|
15
|
+
return nextItem.done ? undefined : nextItem.value;
|
16
|
+
},
|
17
|
+
onAccept(listener) {
|
18
|
+
listeners.push(listener);
|
19
|
+
},
|
20
|
+
setLabel(context) {
|
21
|
+
currentContext = context;
|
22
|
+
},
|
23
|
+
getLabel() {
|
24
|
+
return currentContext;
|
25
|
+
},
|
26
|
+
next() {
|
27
|
+
const value = self.peek();
|
28
|
+
if (currentItem) {
|
29
|
+
listeners.forEach((cb) => cb(currentItem, currentContext));
|
30
|
+
}
|
31
|
+
currentItem = nextItem.done ? undefined : nextItem.value;
|
32
|
+
nextItem = iterator.next();
|
33
|
+
return value;
|
34
|
+
},
|
35
|
+
};
|
36
|
+
return self;
|
37
|
+
};
|