@tolgee/cli 2.0.3 → 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.
Files changed (91) hide show
  1. package/dist/cli.js +15 -23
  2. package/dist/commands/extract/check.js +2 -6
  3. package/dist/commands/extract/print.js +2 -6
  4. package/dist/commands/pull.js +2 -4
  5. package/dist/commands/sync/compare.js +2 -6
  6. package/dist/commands/sync/sync.js +1 -5
  7. package/dist/commands/tag.js +1 -5
  8. package/dist/config/tolgeerc.js +4 -6
  9. package/dist/extractor/extractor.js +40 -66
  10. package/dist/extractor/parser/extractComment.js +78 -0
  11. package/dist/extractor/parser/generalMapper.js +82 -0
  12. package/dist/extractor/parser/generateReport.js +161 -0
  13. package/dist/extractor/parser/iterator.js +37 -0
  14. package/dist/extractor/parser/mergerMachine.js +66 -0
  15. package/dist/extractor/parser/nodeUtils.js +21 -0
  16. package/dist/extractor/parser/parser.js +129 -0
  17. package/dist/extractor/parser/rules/tComponentGeneral.js +45 -0
  18. package/dist/extractor/parser/rules/tFunctionGeneral.js +41 -0
  19. package/dist/extractor/parser/rules/tNsSourceGeneral.js +36 -0
  20. package/dist/extractor/parser/tokenMergers/closingTagMerger.js +27 -0
  21. package/dist/extractor/parser/tokenMergers/commentsMerger.js +36 -0
  22. package/dist/extractor/parser/tokenMergers/stringMerger.js +50 -0
  23. package/dist/extractor/parser/tokenMergers/templateStringMerger.js +55 -0
  24. package/dist/extractor/parser/tokenMergers/typesAsMergerer.js +40 -0
  25. package/dist/extractor/parser/tokenMergers/typesCastMerger.js +28 -0
  26. package/dist/extractor/parser/tree/getTranslateProps.js +54 -0
  27. package/dist/extractor/parser/tree/getValue.js +17 -0
  28. package/dist/extractor/parser/tree/parseGeneral.js +51 -0
  29. package/dist/extractor/parser/tree/parseList.js +33 -0
  30. package/dist/extractor/parser/tree/parseObject.js +130 -0
  31. package/dist/extractor/parser/tree/parseProps.js +56 -0
  32. package/dist/extractor/parser/tree/parseTag.js +26 -0
  33. package/dist/extractor/parser/types.js +1 -0
  34. package/dist/extractor/parserReact/ParserReact.js +30 -0
  35. package/dist/extractor/parserReact/jsxMapper.js +24 -0
  36. package/dist/extractor/parserReact/rules/createElement.js +62 -0
  37. package/dist/extractor/parserReact/rules/tComponent.js +9 -0
  38. package/dist/extractor/parserReact/rules/tFunction.js +7 -0
  39. package/dist/extractor/parserReact/rules/useTranslate.js +7 -0
  40. package/dist/extractor/parserReact/tokenMergers/createElementMerger.js +35 -0
  41. package/dist/extractor/parserReact/tokenMergers/tComponentMerger.js +20 -0
  42. package/dist/extractor/parserReact/tokenMergers/tFunctionMerger.js +23 -0
  43. package/dist/extractor/parserReact/tokenMergers/useTranslateMerger.js +20 -0
  44. package/dist/extractor/parserSvelte/ParserSvelte.js +32 -0
  45. package/dist/extractor/parserSvelte/contextConstants.js +1 -0
  46. package/dist/extractor/parserSvelte/rules/scriptTag.js +26 -0
  47. package/dist/extractor/parserSvelte/rules/tComponent.js +9 -0
  48. package/dist/extractor/parserSvelte/rules/tFunction.js +7 -0
  49. package/dist/extractor/parserSvelte/rules/useTranslate.js +7 -0
  50. package/dist/extractor/parserSvelte/svelteMapper.js +39 -0
  51. package/dist/extractor/parserSvelte/svelteTreeTransform.js +38 -0
  52. package/dist/extractor/parserSvelte/tokenMergers/getTranslateMerger.js +20 -0
  53. package/dist/extractor/parserSvelte/tokenMergers/scriptTagMerger.js +20 -0
  54. package/dist/extractor/parserSvelte/tokenMergers/tComponentMerger.js +20 -0
  55. package/dist/extractor/parserSvelte/tokenMergers/tFunctionMerger.js +29 -0
  56. package/dist/extractor/parserVue/ParserVue.js +45 -0
  57. package/dist/extractor/parserVue/contextConstants.js +3 -0
  58. package/dist/extractor/parserVue/rules/exportDefaultObject.js +14 -0
  59. package/dist/extractor/parserVue/rules/globalTFunction.js +7 -0
  60. package/dist/extractor/parserVue/rules/scriptTag.js +31 -0
  61. package/dist/extractor/parserVue/rules/tComponent.js +9 -0
  62. package/dist/extractor/parserVue/rules/tFunction.js +7 -0
  63. package/dist/extractor/parserVue/rules/useTranslate.js +7 -0
  64. package/dist/extractor/parserVue/tokenMergers/exportDefaultObjectMerger.js +25 -0
  65. package/dist/extractor/parserVue/tokenMergers/globalTFunctionMerger.js +36 -0
  66. package/dist/extractor/parserVue/tokenMergers/scriptTagMerger.js +20 -0
  67. package/dist/extractor/parserVue/tokenMergers/tComponentMerger.js +20 -0
  68. package/dist/extractor/parserVue/tokenMergers/tFunctionMerger.js +37 -0
  69. package/dist/extractor/parserVue/tokenMergers/useTranslateMerger.js +20 -0
  70. package/dist/extractor/parserVue/vueMapper.js +51 -0
  71. package/dist/extractor/parserVue/vueTreeTransform.js +109 -0
  72. package/dist/extractor/runner.js +52 -4
  73. package/dist/extractor/visualizers/printTokens.js +7 -0
  74. package/dist/extractor/visualizers/tokensToString.js +28 -0
  75. package/dist/extractor/visualizers/visualizeRules.js +40 -0
  76. package/dist/extractor/warnings.js +4 -0
  77. package/dist/extractor/worker.js +10 -7
  78. package/dist/options.js +5 -0
  79. package/extractor.d.ts +8 -1
  80. package/package.json +2 -4
  81. package/schema.json +18 -6
  82. package/dist/client/internal/requester.js +0 -130
  83. package/dist/extractor/machines/comments.js +0 -78
  84. package/dist/extractor/machines/react.js +0 -705
  85. package/dist/extractor/machines/shared/comments.js +0 -79
  86. package/dist/extractor/machines/shared/properties.js +0 -380
  87. package/dist/extractor/machines/shared/translateCall.js +0 -141
  88. package/dist/extractor/machines/svelte.js +0 -429
  89. package/dist/extractor/machines/vue/decoder.js +0 -194
  90. package/dist/extractor/machines/vue/extract.js +0 -491
  91. 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
- const config = await loadConfig(program);
126
- program.hook('preAction', preHandler(config));
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 { exitWithError, loading } from '../../utils/logger.js';
5
+ import { loading } from '../../utils/logger.js';
6
6
  const lintHandler = (config) => async function () {
7
7
  const opts = this.optsWithGlobals();
8
- const patterns = opts.patterns;
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 { exitWithError, loading } from '../../utils/logger.js';
5
+ import { loading } from '../../utils/logger.js';
6
6
  const printHandler = (config) => async function () {
7
7
  const opts = this.optsWithGlobals();
8
- const patterns = opts.patterns;
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) {
@@ -14,7 +14,7 @@ async function fetchZipBlob(opts) {
14
14
  supportArrays: opts.supportArrays,
15
15
  languages: opts.languages,
16
16
  filterState: opts.states,
17
- structureDelimiter: opts.delimiter,
17
+ structureDelimiter: opts.delimiter ?? '',
18
18
  filterNamespace: opts.namespaces,
19
19
  filterTagIn: opts.tags,
20
20
  filterTagNotIn: opts.excludeTags,
@@ -43,9 +43,7 @@ export default (config) => new Command()
43
43
  .choices(['UNTRANSLATED', 'TRANSLATED', 'REVIEWED'])
44
44
  .default(config.pull?.states)
45
45
  .argParser((v, a) => [v.toUpperCase(), ...(a || [])]))
46
- .addOption(new Option('-d, --delimiter', 'Structure delimiter to use. By default, Tolgee interprets `.` as a nested structure. You can change the delimiter, or disable structure formatting by not specifying any value to the option')
47
- .default(config.pull?.delimiter ?? '.')
48
- .argParser((v) => v || ''))
46
+ .addOption(new Option('-d, --delimiter <delimiter>', 'Structure delimiter to use. By default, Tolgee interprets `.` as a nested structure. You can change the delimiter, or disable structure formatting by not specifying any value to the option').default(config.pull?.delimiter === undefined ? '.' : config.pull.delimiter))
49
47
  .addOption(new Option('-n, --namespaces <namespaces...>', 'List of namespaces to pull. Defaults to all namespaces').default(config.pull?.namespaces))
50
48
  .addOption(new Option('-t, --tags <tags...>', 'List of tags which to include. Keys tagged by at least one of these tags will be included.').default(config.pull?.tags))
51
49
  .addOption(new Option('--exclude-tags <tags...>', 'List of tags which to exclude. Keys tagged by at least one of these tags will be excluded.').default(config.pull?.excludeTags))
@@ -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 { exitWithError, loading } from '../../utils/logger.js';
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 patterns = opts.patterns?.length ? opts.patterns : config.patterns;
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 patterns = opts.patterns?.length ? opts.patterns : config.patterns;
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.'));
@@ -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 patterns = opts.patterns;
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,
@@ -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
- if (rc.delimiter !== undefined) {
36
- rc.delimiter = rc.delimiter || '';
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
- const REACT_EXTS = [
10
- '.js',
11
- '.mjs',
12
- '.cjs',
13
- '.ts',
14
- '.mts',
15
- '.cts',
16
- '.jsx',
17
- '.tsx',
18
- ];
19
- const VUE_EXTS = REACT_EXTS;
20
- const ALL_EXTS = [
21
- '.js',
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 default async function extractor(code, fileName) {
48
- const ext = extname(fileName);
49
- if (ext === '.vue' &&
50
- (code.includes('$t') ||
51
- code.includes('@tolgee/vue') ||
52
- code.includes('@tolgee-key') ||
53
- code.includes('@tolgee-ignore'))) {
54
- return vueSfcProcessor(code, fileName);
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 machineSpec = pickMachine(code, ext);
57
- if (!machineSpec) {
58
- return { warnings: [], keys: [] };
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
- const tokens = await tokenizer(code, fileName);
61
- // @ts-ignore -- Types are whacky, complains about withConfig but it's not a problem here.
62
- const machine = interpret(machineSpec);
63
- machine.start();
64
- for (const token of tokens) {
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
+ }