@tsslint/cli 1.4.6 → 1.5.1

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/index.js CHANGED
@@ -9,8 +9,10 @@ const glob = require("glob");
9
9
  const fs = require("fs");
10
10
  const os = require("os");
11
11
  const languagePlugins = require("./lib/languagePlugins.js");
12
+ const formatting_js_1 = require("./lib/formatting.js");
12
13
  const _reset = '\x1b[0m';
13
14
  const purple = (s) => '\x1b[35m' + s + _reset;
15
+ const cyan = (s) => '\x1b[36m' + s + _reset;
14
16
  const darkGray = (s) => '\x1b[90m' + s + _reset;
15
17
  const lightRed = (s) => '\x1b[91m' + s + _reset;
16
18
  const lightGreen = (s) => '\x1b[92m' + s + _reset;
@@ -31,20 +33,14 @@ if (process.argv.includes('--threads')) {
31
33
  threads = Math.min(os.availableParallelism(), Number(threadsArg));
32
34
  }
33
35
  class Project {
34
- constructor(tsconfigOption, languages) {
36
+ constructor(tsconfig, languages) {
37
+ this.tsconfig = tsconfig;
35
38
  this.languages = languages;
36
39
  this.workers = [];
37
40
  this.fileNames = [];
38
41
  this.options = {};
39
42
  this.currentFileIndex = 0;
40
43
  this.cache = {};
41
- try {
42
- this.tsconfig = require.resolve(tsconfigOption, { paths: [process.cwd()] });
43
- }
44
- catch {
45
- console.error(lightRed(`No such file: ${tsconfigOption}`));
46
- process.exit(1);
47
- }
48
44
  }
49
45
  async init(
50
46
  // @ts-expect-error
@@ -88,6 +84,8 @@ class Project {
88
84
  const builtConfigs = new Map();
89
85
  const clack = await import('@clack/prompts');
90
86
  const processFiles = new Set();
87
+ const tsconfigAndLanguages = new Map();
88
+ const formattingSettings = getFormattingSettings();
91
89
  let projects = [];
92
90
  let spinner = clack.spinner();
93
91
  let lastSpinnerUpdate = Date.now();
@@ -99,15 +97,16 @@ class Project {
99
97
  let errors = 0;
100
98
  let warnings = 0;
101
99
  let cached = 0;
102
- const tsconfigAndLanguages = new Map();
103
- if (!process.argv.includes('--project')
104
- && !process.argv.includes('--projects')
105
- && !process.argv.includes('--vue-project')
106
- && !process.argv.includes('--vue-projects')
107
- && !process.argv.includes('--mdx-project')
108
- && !process.argv.includes('--mdx-projects')
109
- && !process.argv.includes('--astro-project')
110
- && !process.argv.includes('--astro-projects')) {
100
+ if (![
101
+ '--project',
102
+ '--projects',
103
+ '--vue-project',
104
+ '--vue-projects',
105
+ '--mdx-project',
106
+ '--mdx-projects',
107
+ '--astro-project',
108
+ '--astro-projects',
109
+ ].some(flag => process.argv.includes(flag))) {
111
110
  const languages = await clack.multiselect({
112
111
  required: false,
113
112
  message: 'Select frameworks (optional)',
@@ -154,21 +153,11 @@ class Project {
154
153
  }
155
154
  let command = 'tsslint';
156
155
  if (!languages.length) {
157
- if (selectedTsconfigs.length === 1) {
158
- command += ' --project ' + selectedTsconfigs[0];
159
- }
160
- else {
161
- command += ' --projects ' + selectedTsconfigs.join(' ');
162
- }
156
+ command += ' --project ' + selectedTsconfigs.join(' ');
163
157
  }
164
158
  else {
165
159
  for (const language of languages) {
166
- if (selectedTsconfigs.length === 1) {
167
- command += ` --${language}-project ` + selectedTsconfigs[0];
168
- }
169
- else {
170
- command += ` --${language}-projects ` + selectedTsconfigs.join(' ');
171
- }
160
+ command += ` --${language}-project ` + selectedTsconfigs.join(' ');
172
161
  }
173
162
  }
174
163
  clack.log.info(`Running: ${purple(command)}`);
@@ -182,75 +171,55 @@ class Project {
182
171
  else {
183
172
  const options = [
184
173
  {
185
- projectFlag: '--project',
186
- projectsFlag: '--projects',
174
+ projectFlags: ['--project', '--projects'],
187
175
  language: undefined,
188
176
  },
189
177
  {
190
- projectFlag: '--vue-project',
191
- projectsFlag: '--vue-projects',
178
+ projectFlags: ['--vue-project', '--vue-projects'],
192
179
  language: 'vue',
193
180
  },
194
181
  {
195
- projectFlag: '--mdx-project',
182
+ projectFlags: ['--mdx-project', '--mdx-projects'],
196
183
  projectsFlag: '--mdx-projects',
197
184
  language: 'mdx',
198
185
  },
199
186
  {
200
- projectFlag: '--astro-project',
201
- projectsFlag: '--astro-projects',
187
+ projectFlags: ['--astro-project', '--astro-projects'],
202
188
  language: 'astro',
203
189
  },
204
190
  ];
205
- for (const { projectFlag, projectsFlag, language } of options) {
206
- if (process.argv.includes(projectFlag)) {
207
- const projectIndex = process.argv.indexOf(projectFlag);
208
- let tsconfig = process.argv[projectIndex + 1];
209
- if (!tsconfig || tsconfig.startsWith('-')) {
210
- clack.log.error(lightRed(`Missing argument for ${projectFlag}.`));
211
- process.exit(1);
212
- }
213
- if (!tsconfig.startsWith('.')) {
214
- tsconfig = `./${tsconfig}`;
215
- }
216
- if (!tsconfigAndLanguages.has(tsconfig)) {
217
- tsconfigAndLanguages.set(tsconfig, []);
191
+ for (const { projectFlags, language } of options) {
192
+ const projectFlag = projectFlags.find(flag => process.argv.includes(flag));
193
+ if (!projectFlag) {
194
+ continue;
195
+ }
196
+ let foundArg = false;
197
+ const projectsIndex = process.argv.indexOf(projectFlag);
198
+ for (let i = projectsIndex + 1; i < process.argv.length; i++) {
199
+ if (process.argv[i].startsWith('-')) {
200
+ break;
218
201
  }
219
- if (language) {
220
- tsconfigAndLanguages.get(tsconfig).push(language);
202
+ foundArg = true;
203
+ const searchGlob = process.argv[i];
204
+ const tsconfigs = glob.sync(searchGlob);
205
+ if (!tsconfigs.length) {
206
+ clack.log.error(lightRed(`No projects found for ${projectFlag} ${searchGlob}.`));
207
+ process.exit(1);
221
208
  }
222
- }
223
- if (process.argv.includes(projectsFlag)) {
224
- const projectsIndex = process.argv.indexOf(projectsFlag);
225
- let foundArg = false;
226
- for (let i = projectsIndex + 1; i < process.argv.length; i++) {
227
- if (process.argv[i].startsWith('-')) {
228
- break;
209
+ for (let tsconfig of tsconfigs) {
210
+ tsconfig = resolvePath(tsconfig);
211
+ if (!tsconfigAndLanguages.has(tsconfig)) {
212
+ tsconfigAndLanguages.set(tsconfig, []);
229
213
  }
230
- foundArg = true;
231
- const searchGlob = process.argv[i];
232
- const tsconfigs = glob.sync(searchGlob);
233
- if (!tsconfigs.length) {
234
- clack.log.error(lightRed(`No projects found for ${projectsFlag} ${searchGlob}.`));
235
- process.exit(1);
214
+ if (language) {
215
+ tsconfigAndLanguages.get(tsconfig).push(language);
236
216
  }
237
- for (let tsconfig of tsconfigs) {
238
- if (!tsconfig.startsWith('.')) {
239
- tsconfig = `./${tsconfig}`;
240
- }
241
- if (!tsconfigAndLanguages.has(tsconfig)) {
242
- tsconfigAndLanguages.set(tsconfig, []);
243
- }
244
- if (language) {
245
- tsconfigAndLanguages.get(tsconfig).push(language);
246
- }
247
- }
248
- }
249
- if (!foundArg) {
250
- clack.log.error(lightRed(`Missing argument for ${projectsFlag}.`));
251
- process.exit(1);
252
217
  }
253
218
  }
219
+ if (!foundArg) {
220
+ clack.log.error(lightRed(`Missing argument for ${projectFlag}.`));
221
+ process.exit(1);
222
+ }
254
223
  }
255
224
  }
256
225
  for (const [tsconfig, languages] of tsconfigAndLanguages) {
@@ -270,17 +239,26 @@ class Project {
270
239
  spinner.stop(lightYellow('No input files.'));
271
240
  process.exit(1);
272
241
  }
273
- if (threads === 1) {
274
- await startWorker(worker.createLocal());
242
+ await Promise.all(new Array(threads).fill(0).map(() => {
243
+ return startWorker(worker.create());
244
+ }));
245
+ spinner.stop(cached
246
+ ? darkGray(`Processed ${processed} files with cache. (Use `) + cyan(`--force`) + darkGray(` to ignore cache.)`)
247
+ : darkGray(`Processed ${processed} files.`));
248
+ if (process.argv.includes('--fix') && !formattingSettings) {
249
+ const vscodeSettings = ts.findConfigFile(process.cwd(), ts.sys.fileExists, '.vscode/settings.json');
250
+ if (vscodeSettings) {
251
+ clack.log.message(darkGray(`Found available editor settings, you can use `) + cyan(`--vscode-settings ${path.relative(process.cwd(), vscodeSettings)}`) + darkGray(` to enable code format.`));
252
+ }
275
253
  }
276
- else {
277
- await Promise.all(new Array(threads).fill(0).map(() => {
278
- return startWorker(worker.create());
279
- }));
254
+ const projectsFlag = process.argv.find(arg => arg.endsWith('-projects'));
255
+ if (projectsFlag) {
256
+ clack.log.warn(darkGray(`Please use `)
257
+ + cyan(`${projectsFlag.slice(0, -1)}`)
258
+ + darkGray(` instead of `)
259
+ + cyan(`${projectsFlag}`)
260
+ + darkGray(` starting from version 1.5.0.`));
280
261
  }
281
- spinner.stop(darkGray(cached
282
- ? `Processed ${processed} files with cache. (Use --force to ignore cache.)`
283
- : `Processed ${processed} files.`));
284
262
  const data = [
285
263
  [passed, 'passed', lightGreen],
286
264
  [errors, 'errors', lightRed],
@@ -292,7 +270,7 @@ class Project {
292
270
  .map(([count, label, color]) => color(`${count} ${label}`))
293
271
  .join(darkGray(' | '));
294
272
  if (hasFix) {
295
- summary += darkGray(` (Use --fix to apply automatic fixes.)`);
273
+ summary += darkGray(` (Use `) + cyan(`--fix`) + darkGray(` to apply automatic fixes.)`);
296
274
  }
297
275
  else if (errors || warnings) {
298
276
  summary += darkGray(` (No fixes available.)`);
@@ -315,7 +293,7 @@ class Project {
315
293
  })[0];
316
294
  }
317
295
  project.workers.push(linterWorker);
318
- const setupSuccess = await linterWorker.setup(project.tsconfig, project.languages, project.configFile, project.builtConfig, project.fileNames, project.options);
296
+ const setupSuccess = await linterWorker.setup(project.tsconfig, project.languages, project.configFile, project.builtConfig, project.fileNames, project.options, formattingSettings);
319
297
  if (!setupSuccess) {
320
298
  projects = projects.filter(p => p !== project);
321
299
  startWorker(linterWorker);
@@ -324,7 +302,10 @@ class Project {
324
302
  while (project.currentFileIndex < project.fileNames.length) {
325
303
  const i = project.currentFileIndex++;
326
304
  const fileName = project.fileNames[i];
327
- const fileMtime = fs.statSync(fileName).mtimeMs;
305
+ const fileStat = fs.statSync(fileName, { throwIfNoEntry: false });
306
+ if (!fileStat) {
307
+ continue;
308
+ }
328
309
  addProcessFile(fileName);
329
310
  if (Date.now() - lastSpinnerUpdate > 100) {
330
311
  lastSpinnerUpdate = Date.now();
@@ -332,8 +313,8 @@ class Project {
332
313
  }
333
314
  let fileCache = project.cache[fileName];
334
315
  if (fileCache) {
335
- if (fileCache[0] !== fileMtime) {
336
- fileCache[0] = fileMtime;
316
+ if (fileCache[0] !== fileStat.mtimeMs) {
317
+ fileCache[0] = fileStat.mtimeMs;
337
318
  fileCache[1] = {};
338
319
  fileCache[2] = {};
339
320
  }
@@ -342,15 +323,9 @@ class Project {
342
323
  }
343
324
  }
344
325
  else {
345
- project.cache[fileName] = fileCache = [fileMtime, {}, {}];
346
- }
347
- let diagnostics;
348
- if (process.argv.includes('--fix')) {
349
- diagnostics = await linterWorker.lintAndFix(fileName, fileCache);
350
- }
351
- else {
352
- diagnostics = await linterWorker.lint(fileName, fileCache);
326
+ project.cache[fileName] = fileCache = [fileStat.mtimeMs, {}, {}, false];
353
327
  }
328
+ let diagnostics = await linterWorker.lint(fileName, process.argv.includes('--fix'), fileCache);
354
329
  diagnostics = diagnostics.filter(diagnostic => diagnostic.category !== ts.DiagnosticCategory.Suggestion);
355
330
  if (diagnostics.length) {
356
331
  hasFix ||= await linterWorker.hasCodeFixes(fileName);
@@ -388,6 +363,60 @@ class Project {
388
363
  cache.saveCache(project.tsconfig, project.configFile, project.cache, ts.sys.createHash);
389
364
  await startWorker(linterWorker);
390
365
  }
366
+ function getFormattingSettings() {
367
+ let formattingSettings;
368
+ if (process.argv.includes('--vscode-settings')) {
369
+ formattingSettings = {
370
+ typescript: {},
371
+ javascript: {},
372
+ vue: {},
373
+ };
374
+ for (const section of ['typescript', 'javascript']) {
375
+ formattingSettings[section] = {
376
+ ...ts.getDefaultFormatCodeSettings('\n'),
377
+ indentStyle: ts.IndentStyle.Smart,
378
+ newLineCharacter: '\n',
379
+ insertSpaceAfterCommaDelimiter: true,
380
+ insertSpaceAfterConstructor: false,
381
+ insertSpaceAfterSemicolonInForStatements: true,
382
+ insertSpaceBeforeAndAfterBinaryOperators: true,
383
+ insertSpaceAfterKeywordsInControlFlowStatements: true,
384
+ insertSpaceAfterFunctionKeywordForAnonymousFunctions: true,
385
+ insertSpaceBeforeFunctionParenthesis: false,
386
+ insertSpaceAfterOpeningAndBeforeClosingNonemptyParenthesis: false,
387
+ insertSpaceAfterOpeningAndBeforeClosingNonemptyBrackets: false,
388
+ insertSpaceAfterOpeningAndBeforeClosingNonemptyBraces: true,
389
+ insertSpaceAfterOpeningAndBeforeClosingEmptyBraces: true,
390
+ insertSpaceAfterOpeningAndBeforeClosingTemplateStringBraces: false,
391
+ insertSpaceAfterOpeningAndBeforeClosingJsxExpressionBraces: false,
392
+ insertSpaceAfterTypeAssertion: false,
393
+ placeOpenBraceOnNewLineForFunctions: false,
394
+ placeOpenBraceOnNewLineForControlBlocks: false,
395
+ semicolons: ts.SemicolonPreference.Ignore,
396
+ };
397
+ }
398
+ let vscodeSettingsConfig = process.argv[process.argv.indexOf('--vscode-settings') + 1];
399
+ if (!vscodeSettingsConfig || vscodeSettingsConfig.startsWith('-')) {
400
+ clack.log.error(lightRed(`Missing argument for --vscode-settings.`));
401
+ process.exit(1);
402
+ }
403
+ const vscodeSettingsFile = resolvePath(vscodeSettingsConfig);
404
+ const vscodeSettings = (0, formatting_js_1.getVSCodeFormattingSettings)(vscodeSettingsFile);
405
+ formattingSettings.typescript = {
406
+ ...formattingSettings.typescript,
407
+ ...vscodeSettings.typescript,
408
+ };
409
+ formattingSettings.javascript = {
410
+ ...formattingSettings.javascript,
411
+ ...vscodeSettings.javascript,
412
+ };
413
+ formattingSettings.vue = {
414
+ ...formattingSettings.vue,
415
+ ...vscodeSettings.vue,
416
+ };
417
+ }
418
+ return formattingSettings;
419
+ }
391
420
  async function getBuiltConfig(configFile) {
392
421
  if (!builtConfigs.has(configFile)) {
393
422
  builtConfigs.set(configFile, core.buildConfig(configFile, ts.sys.createHash, spinner, (s, code) => log(darkGray(s), code)));
@@ -416,6 +445,20 @@ class Project {
416
445
  spinner = clack.spinner();
417
446
  spinner.start();
418
447
  }
448
+ function resolvePath(p) {
449
+ if (!path.isAbsolute(p)
450
+ && !p.startsWith('./')
451
+ && !p.startsWith('../')) {
452
+ p = `./${p}`;
453
+ }
454
+ try {
455
+ return require.resolve(p, { paths: [process.cwd()] });
456
+ }
457
+ catch {
458
+ clack.log.error(lightRed(`No such file: ${p}`));
459
+ process.exit(1);
460
+ }
461
+ }
419
462
  })();
420
463
  async function parseCommonLine(tsconfig, languages) {
421
464
  const jsonConfigFile = ts.readJsonConfigFile(tsconfig, ts.sys.readFile);
@@ -0,0 +1,9 @@
1
+ export interface CliOptions {
2
+ threads: number;
3
+ force: boolean;
4
+ fix: boolean;
5
+ vscodeSettings?: string;
6
+ projects: Map<string, string[]>;
7
+ }
8
+ export declare function parseProjectOptions(argv: string[], options: CliOptions): void;
9
+ export declare function parseCliOptions(argv: string[]): CliOptions;
@@ -0,0 +1,55 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.parseProjectOptions = parseProjectOptions;
4
+ exports.parseCliOptions = parseCliOptions;
5
+ const os = require("os");
6
+ function parseProjectOptions(argv, options) {
7
+ const projectFlags = [
8
+ { flags: ['--project', '--projects'], language: undefined },
9
+ { flags: ['--vue-project', '--vue-projects'], language: 'vue' },
10
+ { flags: ['--mdx-project', '--mdx-projects'], language: 'mdx' },
11
+ { flags: ['--astro-project', '--astro-projects'], language: 'astro' }
12
+ ];
13
+ for (const { flags, language } of projectFlags) {
14
+ const flag = flags.find(f => argv.includes(f));
15
+ if (!flag)
16
+ continue;
17
+ const flagIndex = argv.indexOf(flag);
18
+ for (let i = flagIndex + 1; i < argv.length && !argv[i].startsWith('-'); i++) {
19
+ const tsconfig = argv[i];
20
+ if (!options.projects.has(tsconfig)) {
21
+ options.projects.set(tsconfig, []);
22
+ }
23
+ if (language) {
24
+ options.projects.get(tsconfig).push(language);
25
+ }
26
+ }
27
+ }
28
+ }
29
+ function parseCliOptions(argv) {
30
+ const options = {
31
+ threads: 1,
32
+ force: argv.includes('--force'),
33
+ fix: argv.includes('--fix'),
34
+ projects: new Map()
35
+ };
36
+ if (argv.includes('--threads')) {
37
+ const threadsIndex = argv.indexOf('--threads');
38
+ const threadsArg = argv[threadsIndex + 1];
39
+ if (!threadsArg || threadsArg.startsWith('-')) {
40
+ throw new Error('Missing argument for --threads');
41
+ }
42
+ options.threads = Math.min(os.availableParallelism(), Number(threadsArg));
43
+ }
44
+ if (argv.includes('--vscode-settings')) {
45
+ const settingsIndex = argv.indexOf('--vscode-settings');
46
+ const settingsPath = argv[settingsIndex + 1];
47
+ if (!settingsPath || settingsPath.startsWith('-')) {
48
+ throw new Error('Missing argument for --vscode-settings');
49
+ }
50
+ options.vscodeSettings = settingsPath;
51
+ }
52
+ parseProjectOptions(argv, options);
53
+ return options;
54
+ }
55
+ //# sourceMappingURL=cli-options.js.map
@@ -0,0 +1,9 @@
1
+ import type * as ts from 'typescript';
2
+ export declare function getVSCodeFormattingSettings(settingsFile: string): {
3
+ javascript: ts.FormatCodeSettings;
4
+ typescript: ts.FormatCodeSettings;
5
+ vue: {
6
+ 'script.initialIndent'?: boolean;
7
+ };
8
+ };
9
+ export declare function computeInitialIndent(content: string, i: number, baseTabSize?: number): number;
@@ -0,0 +1,60 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.getVSCodeFormattingSettings = getVSCodeFormattingSettings;
4
+ exports.computeInitialIndent = computeInitialIndent;
5
+ const fs = require("fs");
6
+ const json5 = require("json5");
7
+ function getVSCodeFormattingSettings(settingsFile) {
8
+ const jsonc = fs.readFileSync(settingsFile, 'utf8');
9
+ const editorSettings = json5.parse(jsonc);
10
+ const jsSettings = {};
11
+ const tsSettings = {};
12
+ const vueSettings = {};
13
+ if ('editor.insertSpaces' in editorSettings) {
14
+ jsSettings.convertTabsToSpaces = !!editorSettings['editor.insertSpaces'];
15
+ tsSettings.convertTabsToSpaces = !!editorSettings['editor.insertSpaces'];
16
+ }
17
+ if ('editor.tabSize' in editorSettings) {
18
+ jsSettings.tabSize = editorSettings['editor.tabSize'];
19
+ tsSettings.tabSize = editorSettings['editor.tabSize'];
20
+ }
21
+ for (const key in editorSettings) {
22
+ if (key.startsWith('javascript.format.')) {
23
+ const settingKey = key.slice('javascript.format.'.length);
24
+ // @ts-expect-error
25
+ jsSettings[settingKey] = editorSettings[key];
26
+ }
27
+ else if (key.startsWith('typescript.format.')) {
28
+ const settingKey = key.slice('typescript.format.'.length);
29
+ // @ts-expect-error
30
+ tsSettings[settingKey] = editorSettings[key];
31
+ }
32
+ }
33
+ if ('vue.format.script.initialIndent' in editorSettings) {
34
+ vueSettings['script.initialIndent'] = !!editorSettings['vue.format.script.initialIndent'];
35
+ }
36
+ return {
37
+ javascript: jsSettings,
38
+ typescript: tsSettings,
39
+ vue: vueSettings,
40
+ };
41
+ }
42
+ function computeInitialIndent(content, i, baseTabSize) {
43
+ let nChars = 0;
44
+ const tabSize = baseTabSize || 4;
45
+ while (i < content.length) {
46
+ const ch = content.charAt(i);
47
+ if (ch === ' ') {
48
+ nChars++;
49
+ }
50
+ else if (ch === '\t') {
51
+ nChars += tabSize;
52
+ }
53
+ else {
54
+ break;
55
+ }
56
+ i++;
57
+ }
58
+ return Math.floor(nChars / tabSize);
59
+ }
60
+ //# sourceMappingURL=formatting.js.map
@@ -0,0 +1,25 @@
1
+ export interface SpinnerHandle {
2
+ start(text?: string): void;
3
+ stop(text?: string): void;
4
+ }
5
+ export declare const purple: (s: string) => string;
6
+ export declare const cyan: (s: string) => string;
7
+ export declare const darkGray: (s: string) => string;
8
+ export declare const lightRed: (s: string) => string;
9
+ export declare const lightGreen: (s: string) => string;
10
+ export declare const lightYellow: (s: string) => string;
11
+ export declare const colors: {
12
+ ts: (s: string) => string;
13
+ vue: (s: string) => string;
14
+ mdx: (s: string) => string;
15
+ astro: (s: string) => string;
16
+ };
17
+ export declare class Logger {
18
+ private createSpinner;
19
+ private spinnerHandle?;
20
+ constructor(createSpinner: () => SpinnerHandle);
21
+ startSpinner(message: string): void;
22
+ error(msg: string): void;
23
+ warn(msg: string): void;
24
+ info(msg: string): void;
25
+ }
package/lib/logger.js ADDED
@@ -0,0 +1,42 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.Logger = exports.colors = exports.lightYellow = exports.lightGreen = exports.lightRed = exports.darkGray = exports.cyan = exports.purple = void 0;
4
+ const _reset = '\x1b[0m';
5
+ const purple = (s) => '\x1b[35m' + s + _reset;
6
+ exports.purple = purple;
7
+ const cyan = (s) => '\x1b[36m' + s + _reset;
8
+ exports.cyan = cyan;
9
+ const darkGray = (s) => '\x1b[90m' + s + _reset;
10
+ exports.darkGray = darkGray;
11
+ const lightRed = (s) => '\x1b[91m' + s + _reset;
12
+ exports.lightRed = lightRed;
13
+ const lightGreen = (s) => '\x1b[92m' + s + _reset;
14
+ exports.lightGreen = lightGreen;
15
+ const lightYellow = (s) => '\x1b[93m' + s + _reset;
16
+ exports.lightYellow = lightYellow;
17
+ exports.colors = {
18
+ ts: (s) => '\x1b[34m' + s + _reset,
19
+ vue: (s) => '\x1b[32m' + s + _reset,
20
+ mdx: (s) => '\x1b[33m' + s + _reset,
21
+ astro: (s) => '\x1b[38;5;209m' + s + _reset
22
+ };
23
+ class Logger {
24
+ constructor(createSpinner) {
25
+ this.createSpinner = createSpinner;
26
+ }
27
+ startSpinner(message) {
28
+ this.spinnerHandle = this.createSpinner();
29
+ this.spinnerHandle.start(message);
30
+ }
31
+ error(msg) {
32
+ this.spinnerHandle?.stop((0, exports.lightRed)(msg));
33
+ }
34
+ warn(msg) {
35
+ this.spinnerHandle?.stop((0, exports.lightYellow)(msg));
36
+ }
37
+ info(msg) {
38
+ this.spinnerHandle?.stop(msg);
39
+ }
40
+ }
41
+ exports.Logger = Logger;
42
+ //# sourceMappingURL=logger.js.map
@@ -0,0 +1,17 @@
1
+ import ts = require('typescript');
2
+ import cache = require('./cache.js');
3
+ import worker = require('./worker.js');
4
+ export declare class Project {
5
+ tsconfig: string;
6
+ languages: string[];
7
+ workers: ReturnType<typeof worker.create>[];
8
+ fileNames: string[];
9
+ options: ts.CompilerOptions;
10
+ configFile: string | undefined;
11
+ currentFileIndex: number;
12
+ builtConfig: string | undefined;
13
+ cache: cache.CacheData;
14
+ constructor(tsconfig: string, languages: string[]);
15
+ init(clack: typeof import('@clack/prompts')): Promise<this>;
16
+ private getProjectLabels;
17
+ }
package/lib/project.js ADDED
@@ -0,0 +1,83 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.Project = void 0;
4
+ const ts = require("typescript");
5
+ const path = require("path");
6
+ const core = require("@tsslint/core");
7
+ const cache = require("./cache.js");
8
+ const languagePlugins = require("./languagePlugins.js");
9
+ const logger_js_1 = require("./logger.js");
10
+ class Project {
11
+ constructor(tsconfig, languages) {
12
+ this.tsconfig = tsconfig;
13
+ this.languages = languages;
14
+ this.workers = [];
15
+ this.fileNames = [];
16
+ this.options = {};
17
+ this.currentFileIndex = 0;
18
+ this.cache = {};
19
+ }
20
+ async init(clack) {
21
+ this.configFile = ts.findConfigFile(path.dirname(this.tsconfig), ts.sys.fileExists, 'tsslint.config.ts');
22
+ const labels = this.getProjectLabels();
23
+ const label = labels.join((0, logger_js_1.darkGray)(' | '));
24
+ if (!this.configFile) {
25
+ clack.log.error(`${label} ${path.relative(process.cwd(), this.tsconfig)} ${(0, logger_js_1.darkGray)('(No tsslint.config.ts found)')}`);
26
+ return this;
27
+ }
28
+ const spinner = clack.spinner();
29
+ try {
30
+ this.builtConfig = await core.buildConfig(this.configFile, ts.sys.createHash, spinner, (s, code) => clack.log.error((0, logger_js_1.darkGray)(s)));
31
+ }
32
+ catch (err) {
33
+ spinner.stop();
34
+ if (err instanceof Error) {
35
+ clack.log.error(err.stack ?? err.message);
36
+ }
37
+ else {
38
+ clack.log.error(String(err));
39
+ }
40
+ return this;
41
+ }
42
+ if (!this.builtConfig) {
43
+ spinner.stop();
44
+ return this;
45
+ }
46
+ const commonLine = await parseCommonLine(this.tsconfig, this.languages);
47
+ this.fileNames = commonLine.fileNames;
48
+ this.options = commonLine.options;
49
+ if (!this.fileNames.length) {
50
+ clack.log.warn(`${label} ${path.relative(process.cwd(), this.tsconfig)} ${(0, logger_js_1.darkGray)('(No included files)')}`);
51
+ return this;
52
+ }
53
+ clack.log.info(`${label} ${path.relative(process.cwd(), this.tsconfig)} ${(0, logger_js_1.darkGray)(`(${this.fileNames.length})`)}`);
54
+ if (!process.argv.includes('--force')) {
55
+ this.cache = cache.loadCache(this.tsconfig, this.configFile, ts.sys.createHash);
56
+ }
57
+ return this;
58
+ }
59
+ getProjectLabels() {
60
+ if (this.languages.length === 0) {
61
+ return [logger_js_1.colors.ts('TS')];
62
+ }
63
+ const labels = [];
64
+ if (this.languages.includes('vue')) {
65
+ labels.push(logger_js_1.colors.vue('Vue'));
66
+ }
67
+ if (this.languages.includes('mdx')) {
68
+ labels.push(logger_js_1.colors.mdx('MDX'));
69
+ }
70
+ if (this.languages.includes('astro')) {
71
+ labels.push(logger_js_1.colors.astro('Astro'));
72
+ }
73
+ return labels;
74
+ }
75
+ }
76
+ exports.Project = Project;
77
+ async function parseCommonLine(tsconfig, languages) {
78
+ const jsonConfigFile = ts.readJsonConfigFile(tsconfig, ts.sys.readFile);
79
+ const plugins = await languagePlugins.load(tsconfig, languages);
80
+ const extraFileExtensions = plugins.flatMap(plugin => plugin.typescript?.extraFileExtensions ?? []).flat();
81
+ return ts.parseJsonSourceFileConfigFileContent(jsonConfigFile, ts.sys, path.dirname(tsconfig), {}, tsconfig, undefined, extraFileExtensions);
82
+ }
83
+ //# sourceMappingURL=project.js.map
@@ -0,0 +1,31 @@
1
+ import type { Project } from './project';
2
+ import type { CliOptions } from './cli-options';
3
+ import type { Logger } from './logger';
4
+ export interface RunnerStats {
5
+ processed: number;
6
+ excluded: number;
7
+ passed: number;
8
+ errors: number;
9
+ warnings: number;
10
+ cached: number;
11
+ hasFix: boolean;
12
+ }
13
+ export declare class Runner {
14
+ private projects;
15
+ private options;
16
+ private logger;
17
+ private stats;
18
+ private processFiles;
19
+ private lastSpinnerUpdate;
20
+ private readonly allFilesNum;
21
+ constructor(projects: Project[], options: CliOptions, logger: Logger);
22
+ run(): Promise<RunnerStats>;
23
+ private startWorker;
24
+ private selectProject;
25
+ private processProject;
26
+ private processFile;
27
+ private addProcessFile;
28
+ private removeProcessFile;
29
+ private updateSpinner;
30
+ private updateStats;
31
+ }
package/lib/runner.js ADDED
@@ -0,0 +1,151 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.Runner = void 0;
4
+ const worker = require("./worker");
5
+ const cache = require("./cache");
6
+ const ts = require("typescript");
7
+ const path = require("path");
8
+ const formatting_1 = require("./formatting");
9
+ const logger_1 = require("./logger");
10
+ class Runner {
11
+ constructor(projects, options, logger) {
12
+ this.projects = projects;
13
+ this.options = options;
14
+ this.logger = logger;
15
+ this.stats = {
16
+ processed: 0,
17
+ excluded: 0,
18
+ passed: 0,
19
+ errors: 0,
20
+ warnings: 0,
21
+ cached: 0,
22
+ hasFix: false
23
+ };
24
+ this.processFiles = new Set();
25
+ this.lastSpinnerUpdate = Date.now();
26
+ this.allFilesNum = projects.reduce((sum, p) => sum + p.fileNames.length, 0);
27
+ }
28
+ async run() {
29
+ if (this.allFilesNum === 0) {
30
+ this.logger.warn('No input files.');
31
+ process.exit(1);
32
+ }
33
+ if (this.options.threads === 1) {
34
+ await this.startWorker(worker.createLocal());
35
+ }
36
+ else {
37
+ await Promise.all(new Array(this.options.threads)
38
+ .fill(0)
39
+ .map(() => this.startWorker(worker.create())));
40
+ }
41
+ return this.stats;
42
+ }
43
+ async startWorker(linterWorker) {
44
+ const unfinishedProjects = this.projects.filter(project => project.currentFileIndex < project.fileNames.length);
45
+ if (!unfinishedProjects.length)
46
+ return;
47
+ const project = this.selectProject(unfinishedProjects);
48
+ project.workers.push(linterWorker);
49
+ const formattingSettings = this.options.vscodeSettings
50
+ ? (0, formatting_1.getVSCodeFormattingSettings)(this.options.vscodeSettings)
51
+ : undefined;
52
+ const setupSuccess = await linterWorker.setup(project.tsconfig, project.languages, project.configFile, project.builtConfig, project.fileNames, project.options, formattingSettings);
53
+ if (!setupSuccess) {
54
+ this.projects = this.projects.filter(p => p !== project);
55
+ await this.startWorker(linterWorker);
56
+ return;
57
+ }
58
+ await this.processProject(project, linterWorker);
59
+ await this.startWorker(linterWorker);
60
+ }
61
+ selectProject(unfinishedProjects) {
62
+ // First try to find a project without workers
63
+ const projectWithoutWorker = unfinishedProjects.find(p => !p.workers.length);
64
+ if (projectWithoutWorker) {
65
+ return projectWithoutWorker;
66
+ }
67
+ // Otherwise choose project with most files left per worker
68
+ return unfinishedProjects.sort((a, b) => {
69
+ const aFilesPerWorker = (a.fileNames.length - a.currentFileIndex) / (a.workers.length || 1);
70
+ const bFilesPerWorker = (b.fileNames.length - b.currentFileIndex) / (b.workers.length || 1);
71
+ return bFilesPerWorker - aFilesPerWorker;
72
+ })[0];
73
+ }
74
+ async processProject(project, linterWorker) {
75
+ while (project.currentFileIndex < project.fileNames.length) {
76
+ await this.processFile(project, project.currentFileIndex++, linterWorker);
77
+ }
78
+ cache.saveCache(project.tsconfig, project.configFile, project.cache, ts.sys.createHash);
79
+ }
80
+ async processFile(project, fileIndex, linterWorker) {
81
+ const fileName = project.fileNames[fileIndex];
82
+ const fileStat = ts.sys.getModifiedTime(fileName);
83
+ this.addProcessFile(fileName);
84
+ if (Date.now() - this.lastSpinnerUpdate > 100) {
85
+ this.lastSpinnerUpdate = Date.now();
86
+ await new Promise(resolve => setTimeout(resolve, 0));
87
+ this.updateSpinner();
88
+ }
89
+ let fileCache = project.cache[fileName];
90
+ if (fileCache) {
91
+ if (fileCache[0] !== fileStat.getTime()) {
92
+ fileCache[0] = fileStat.getTime();
93
+ fileCache[1] = {};
94
+ fileCache[2] = {};
95
+ }
96
+ else {
97
+ this.stats.cached++;
98
+ }
99
+ }
100
+ else {
101
+ project.cache[fileName] = fileCache = [fileStat.getTime(), {}, {}, false];
102
+ }
103
+ const diagnostics = await linterWorker.lint(fileName, this.options.fix, fileCache);
104
+ if (diagnostics.length === 0) {
105
+ const [hasRules] = await linterWorker.hasRules(fileName, fileCache[2]);
106
+ if (!hasRules) {
107
+ this.stats.excluded++;
108
+ this.removeProcessFile(fileName);
109
+ return;
110
+ }
111
+ }
112
+ this.stats.hasFix ||= await linterWorker.hasCodeFixes(fileName);
113
+ this.updateStats(diagnostics);
114
+ this.removeProcessFile(fileName);
115
+ }
116
+ addProcessFile(fileName) {
117
+ this.processFiles.add(fileName);
118
+ this.updateSpinner();
119
+ }
120
+ removeProcessFile(fileName) {
121
+ this.processFiles.delete(fileName);
122
+ this.updateSpinner();
123
+ }
124
+ updateSpinner() {
125
+ if (this.processFiles.size === 1) {
126
+ const fileName = Array.from(this.processFiles)[0];
127
+ this.logger.startSpinner((0, logger_1.darkGray)(`[${this.stats.processed + this.processFiles.size}/${this.allFilesNum}] ${path.relative(process.cwd(), fileName)}`));
128
+ }
129
+ else {
130
+ this.logger.startSpinner((0, logger_1.darkGray)(`[${this.stats.processed + this.processFiles.size}/${this.allFilesNum}] Processing ${this.processFiles.size} files`));
131
+ }
132
+ }
133
+ updateStats(diagnostics) {
134
+ if (diagnostics.length === 0) {
135
+ this.stats.passed++;
136
+ }
137
+ else {
138
+ for (const diagnostic of diagnostics) {
139
+ if (diagnostic.category === ts.DiagnosticCategory.Error) {
140
+ this.stats.errors++;
141
+ }
142
+ else if (diagnostic.category === ts.DiagnosticCategory.Warning) {
143
+ this.stats.warnings++;
144
+ }
145
+ }
146
+ }
147
+ this.stats.processed++;
148
+ }
149
+ }
150
+ exports.Runner = Runner;
151
+ //# sourceMappingURL=runner.js.map
package/lib/worker.d.ts CHANGED
@@ -1,22 +1,32 @@
1
1
  import ts = require('typescript');
2
2
  import core = require('@tsslint/core');
3
+ import { type getVSCodeFormattingSettings } from './formatting.js';
3
4
  export declare function createLocal(): {
4
- setup(tsconfig: string, languages: string[], configFile: string, builtConfig: string, _fileNames: string[], _options: ts.CompilerOptions): Promise<boolean>;
5
- lint(fileName: string, fileCache: core.FileLintCache): ts.DiagnosticWithLocation[];
6
- lintAndFix(fileName: string, fileCache: core.FileLintCache): ts.DiagnosticWithLocation[];
5
+ setup(tsconfig: string, languages: string[], configFile: string, builtConfig: string, _fileNames: string[], _options: ts.CompilerOptions, _fmtSettings: {
6
+ javascript: ts.FormatCodeSettings;
7
+ typescript: ts.FormatCodeSettings;
8
+ vue: {
9
+ 'script.initialIndent'?: boolean;
10
+ };
11
+ } | undefined): Promise<boolean>;
12
+ lint(fileName: string, fix: boolean, fileCache: core.FileLintCache): ts.DiagnosticWithLocation[];
7
13
  hasCodeFixes(fileName: string): boolean;
8
14
  hasRules(fileName: string, minimatchCache: Record<string, boolean>): boolean;
9
15
  };
10
16
  export declare function create(): {
11
- setup(tsconfig: string, languages: string[], configFile: string, builtConfig: string, _fileNames: string[], _options: ts.CompilerOptions): Promise<boolean>;
12
- lint(fileName: string, fileCache: core.FileLintCache): Promise<ts.DiagnosticWithLocation[]>;
13
- lintAndFix(fileName: string, fileCache: core.FileLintCache): Promise<ts.DiagnosticWithLocation[]>;
17
+ setup(tsconfig: string, languages: string[], configFile: string, builtConfig: string, _fileNames: string[], _options: ts.CompilerOptions, _fmtSettings: {
18
+ javascript: ts.FormatCodeSettings;
19
+ typescript: ts.FormatCodeSettings;
20
+ vue: {
21
+ 'script.initialIndent'?: boolean;
22
+ };
23
+ } | undefined): Promise<boolean>;
24
+ lint(fileName: string, fix: boolean, fileCache: core.FileLintCache): Promise<ts.DiagnosticWithLocation[]>;
14
25
  hasCodeFixes(fileName: string): Promise<boolean>;
15
26
  hasRules(fileName: string, minimatchCache: Record<string, boolean>): Promise<boolean>;
16
27
  };
17
- declare function setup(tsconfig: string, languages: string[], configFile: string, builtConfig: string, _fileNames: string[], _options: ts.CompilerOptions): Promise<boolean>;
18
- declare function lintAndFix(fileName: string, fileCache: core.FileLintCache): readonly [ts.DiagnosticWithLocation[], core.FileLintCache];
19
- declare function lint(fileName: string, fileCache: core.FileLintCache): readonly [ts.DiagnosticWithLocation[], core.FileLintCache];
28
+ declare function setup(tsconfig: string, languages: string[], configFile: string, builtConfig: string, _fileNames: string[], _options: ts.CompilerOptions, _fmtSettings: ReturnType<typeof getVSCodeFormattingSettings> | undefined): Promise<boolean>;
29
+ declare function lint(fileName: string, fix: boolean, fileCache: core.FileLintCache): readonly [ts.DiagnosticWithLocation[], core.FileLintCache];
20
30
  declare function hasCodeFixes(fileName: string): boolean;
21
31
  declare function hasRules(fileName: string, minimatchCache: core.FileLintCache[2]): readonly [boolean, Record<string, boolean>];
22
32
  export {};
package/lib/worker.js CHANGED
@@ -6,11 +6,14 @@ const ts = require("typescript");
6
6
  const core = require("@tsslint/core");
7
7
  const url = require("url");
8
8
  const fs = require("fs");
9
+ const path = require("path");
9
10
  const worker_threads = require("worker_threads");
10
11
  const languagePlugins = require("./languagePlugins.js");
11
12
  const language_core_1 = require("@volar/language-core");
12
13
  const typescript_1 = require("@volar/typescript");
13
14
  const transform_1 = require("@volar/typescript/lib/node/transform");
15
+ const transform_js_1 = require("@volar/typescript/lib/node/transform.js");
16
+ const formatting_js_1 = require("./formatting.js");
14
17
  let projectVersion = 0;
15
18
  let typeRootsVersion = 0;
16
19
  let options = {};
@@ -19,6 +22,42 @@ let language;
19
22
  let linter;
20
23
  let linterLanguageService;
21
24
  let linterSyntaxOnlyLanguageService;
25
+ let fmtSettings;
26
+ const formatLanguageService = ts.createLanguageService({
27
+ ...ts.sys,
28
+ getCompilationSettings() {
29
+ return options;
30
+ },
31
+ getScriptFileNames() {
32
+ return [];
33
+ },
34
+ getScriptVersion() {
35
+ return '0';
36
+ },
37
+ getScriptSnapshot() {
38
+ return formattingSnapshot;
39
+ },
40
+ getScriptKind() {
41
+ return formattingScriptKind;
42
+ },
43
+ useCaseSensitiveFileNames() {
44
+ return ts.sys.useCaseSensitiveFileNames;
45
+ },
46
+ getCurrentDirectory() {
47
+ return ts.sys.getCurrentDirectory();
48
+ },
49
+ getDefaultLibFileName() {
50
+ return ts.getDefaultLibFilePath(options);
51
+ },
52
+ }, undefined, true);
53
+ let formattingSnapshot;
54
+ let formattingScriptKind;
55
+ let formattingIndex = 0;
56
+ function formatVirtualScript(kind, settings, snapshot) {
57
+ formattingSnapshot = snapshot;
58
+ formattingScriptKind = kind;
59
+ return formatLanguageService.getFormattingEditsForDocument(`${formattingIndex++}.txt`, settings);
60
+ }
22
61
  const snapshots = new Map();
23
62
  const versions = new Map();
24
63
  const originalHost = {
@@ -78,9 +117,6 @@ function createLocal() {
78
117
  lint(...args) {
79
118
  return lint(...args)[0];
80
119
  },
81
- lintAndFix(...args) {
82
- return lintAndFix(...args)[0];
83
- },
84
120
  hasCodeFixes(...args) {
85
121
  return hasCodeFixes(...args);
86
122
  },
@@ -100,11 +136,6 @@ function create() {
100
136
  Object.assign(args[1], newCache); // Sync the cache
101
137
  return res;
102
138
  },
103
- async lintAndFix(...args) {
104
- const [res, newCache] = await sendRequest(lintAndFix, ...args);
105
- Object.assign(args[1], newCache); // Sync the cache
106
- return res;
107
- },
108
139
  hasCodeFixes(...args) {
109
140
  return sendRequest(hasCodeFixes, ...args);
110
141
  },
@@ -131,11 +162,10 @@ worker_threads.parentPort?.on('message', async (json) => {
131
162
  const handlers = {
132
163
  setup,
133
164
  lint,
134
- lintAndFix,
135
165
  hasCodeFixes,
136
166
  hasRules,
137
167
  };
138
- async function setup(tsconfig, languages, configFile, builtConfig, _fileNames, _options) {
168
+ async function setup(tsconfig, languages, configFile, builtConfig, _fileNames, _options, _fmtSettings) {
139
169
  const clack = await import('@clack/prompts');
140
170
  let config;
141
171
  try {
@@ -192,27 +222,26 @@ async function setup(tsconfig, languages, configFile, builtConfig, _fileNames, _
192
222
  allowNonTsExtensions: true,
193
223
  }
194
224
  : _options;
225
+ fmtSettings = _fmtSettings;
195
226
  linter = core.createLinter({
196
- configFile,
197
227
  languageService: linterLanguageService,
198
228
  languageServiceHost: linterHost,
199
229
  typescript: ts,
200
- tsconfig: ts.server.toNormalizedPath(tsconfig),
201
- }, config, 'cli', linterSyntaxOnlyLanguageService);
230
+ }, path.dirname(configFile), config, 'cli', linterSyntaxOnlyLanguageService);
202
231
  return true;
203
232
  }
204
- function lintAndFix(fileName, fileCache) {
205
- let retry = 1;
206
- let shouldRetry = true;
233
+ function lint(fileName, fix, fileCache) {
207
234
  let newSnapshot;
208
235
  let diagnostics;
209
- while (shouldRetry && retry--) {
236
+ let shouldCheck = true;
237
+ if (fix) {
210
238
  if (Object.values(fileCache[1]).some(([hasFix]) => hasFix)) {
211
239
  // Reset the cache if there are any fixes applied.
212
240
  fileCache[1] = {};
213
241
  fileCache[2] = {};
214
242
  }
215
243
  diagnostics = linter.lint(fileName, fileCache);
244
+ shouldCheck = false;
216
245
  let fixes = linter
217
246
  .getCodeFixes(fileName, 0, Number.MAX_VALUE, diagnostics, fileCache[2])
218
247
  .filter(fix => fix.fixId === 'tsslint');
@@ -224,21 +253,128 @@ function lintAndFix(fileName, fileCache) {
224
253
  }
225
254
  const textChanges = core.combineCodeFixes(fileName, fixes);
226
255
  if (textChanges.length) {
256
+ fileCache[3] = false;
227
257
  const oldSnapshot = snapshots.get(fileName);
228
258
  newSnapshot = core.applyTextChanges(oldSnapshot, textChanges);
229
259
  snapshots.set(fileName, newSnapshot);
230
260
  versions.set(fileName, (versions.get(fileName) ?? 0) + 1);
231
261
  projectVersion++;
232
- shouldRetry = true;
262
+ }
263
+ if (!fileCache[3] && fmtSettings) {
264
+ fileCache[3] = true;
265
+ let script = language?.scripts.get(fileName);
266
+ let linterEdits = [];
267
+ let serviceEdits = [];
268
+ if (script?.generated) {
269
+ for (const code of (0, language_core_1.forEachEmbeddedCode)(script.generated.root)) {
270
+ if ((code.languageId === 'javascript'
271
+ || code.languageId === 'typescript'
272
+ || code.languageId === 'javascriptreact'
273
+ || code.languageId === 'typescriptreact')
274
+ && code.mappings.some(mapping => (0, language_core_1.isFormattingEnabled)(mapping.data))) {
275
+ const scriptKind = code.languageId === 'javascript' ? ts.ScriptKind.JS
276
+ : code.languageId === 'javascriptreact' ? ts.ScriptKind.JSX
277
+ : code.languageId === 'typescript' ? ts.ScriptKind.TS
278
+ : ts.ScriptKind.TSX;
279
+ const sourceFile = ts.createSourceFile(fileName, code.snapshot.getText(0, code.snapshot.getLength()), ts.ScriptTarget.Latest, true, scriptKind);
280
+ linterEdits.push(...linter
281
+ .format(sourceFile, fileCache[2])
282
+ .map(edit => {
283
+ return (0, transform_js_1.transformTextChange)(script, language, {
284
+ code,
285
+ extension: '',
286
+ scriptKind: scriptKind,
287
+ preventLeadingOffset: true,
288
+ }, edit, false, language_core_1.isFormattingEnabled)?.[1];
289
+ })
290
+ .filter(edit => !!edit));
291
+ }
292
+ }
293
+ }
294
+ else {
295
+ const sourceFile = originalSyntaxOnlyService.getNonBoundSourceFile(fileName);
296
+ linterEdits = linter.format(sourceFile, fileCache[2]);
297
+ }
298
+ if (linterEdits.length) {
299
+ const oldSnapshot = snapshots.get(fileName);
300
+ newSnapshot = core.applyTextChanges(oldSnapshot, linterEdits);
301
+ snapshots.set(fileName, newSnapshot);
302
+ versions.set(fileName, (versions.get(fileName) ?? 0) + 1);
303
+ projectVersion++;
304
+ script = language?.scripts.get(fileName);
305
+ }
306
+ if (script?.generated) {
307
+ let sourceFile;
308
+ for (const code of (0, language_core_1.forEachEmbeddedCode)(script.generated.root)) {
309
+ if ((code.languageId === 'javascript'
310
+ || code.languageId === 'typescript'
311
+ || code.languageId === 'javascriptreact'
312
+ || code.languageId === 'typescriptreact')
313
+ && code.mappings.some(mapping => (0, language_core_1.isFormattingEnabled)(mapping.data))) {
314
+ const scriptKind = code.languageId === 'javascript' ? ts.ScriptKind.JS
315
+ : code.languageId === 'javascriptreact' ? ts.ScriptKind.JSX
316
+ : code.languageId === 'typescript' ? ts.ScriptKind.TS
317
+ : ts.ScriptKind.TSX;
318
+ let settings = scriptKind === ts.ScriptKind.JS || scriptKind === ts.ScriptKind.JSX
319
+ ? fmtSettings.javascript
320
+ : fmtSettings.typescript;
321
+ if (settings.tabSize !== undefined) {
322
+ const firstMapping = code.mappings[0];
323
+ sourceFile ??= ts.createSourceFile(fileName, script.snapshot.getText(0, script.snapshot.getLength()), ts.ScriptTarget.Latest, true, ts.ScriptKind.Deferred);
324
+ const line = sourceFile.getLineAndCharacterOfPosition(firstMapping.sourceOffsets[0]).line;
325
+ const offset = sourceFile.getPositionOfLineAndCharacter(line, 0);
326
+ let initialIndentLevel = (0, formatting_js_1.computeInitialIndent)(script.snapshot.getText(0, script.snapshot.getLength()), offset, settings.tabSize);
327
+ if (script.languageId === 'vue'
328
+ && fmtSettings.vue['script.initialIndent']
329
+ && (code.id === 'script_raw'
330
+ || code.id === 'scriptsetup_raw')) {
331
+ initialIndentLevel++;
332
+ }
333
+ settings = {
334
+ ...settings,
335
+ baseIndentSize: initialIndentLevel * settings.tabSize,
336
+ };
337
+ }
338
+ serviceEdits.push(...formatVirtualScript(scriptKind, settings, code.snapshot)
339
+ .map(edit => {
340
+ return (0, transform_js_1.transformTextChange)(script, language, {
341
+ code,
342
+ extension: '',
343
+ scriptKind: scriptKind,
344
+ preventLeadingOffset: true,
345
+ }, edit, false, language_core_1.isFormattingEnabled)?.[1];
346
+ })
347
+ .filter(edit => !!edit));
348
+ }
349
+ }
350
+ }
351
+ else {
352
+ const scriptKind = linterHost.getScriptKind(fileName);
353
+ const settings = scriptKind === ts.ScriptKind.JS || scriptKind === ts.ScriptKind.JSX ? fmtSettings.javascript : fmtSettings.typescript;
354
+ serviceEdits = linterLanguageService.getFormattingEditsForDocument(fileName, settings);
355
+ }
356
+ if (serviceEdits.length) {
357
+ const oldSnapshot = snapshots.get(fileName);
358
+ newSnapshot = core.applyTextChanges(oldSnapshot, serviceEdits);
359
+ snapshots.set(fileName, newSnapshot);
360
+ versions.set(fileName, (versions.get(fileName) ?? 0) + 1);
361
+ projectVersion++;
362
+ }
233
363
  }
234
364
  }
235
365
  if (newSnapshot) {
236
- ts.sys.writeFile(fileName, newSnapshot.getText(0, newSnapshot.getLength()));
237
- fileCache[0] = fs.statSync(fileName).mtimeMs;
238
- fileCache[1] = {};
239
- fileCache[2] = {};
366
+ const newText = newSnapshot.getText(0, newSnapshot.getLength());
367
+ const oldText = ts.sys.readFile(fileName);
368
+ if (newText !== oldText) {
369
+ ts.sys.writeFile(fileName, newSnapshot.getText(0, newSnapshot.getLength()));
370
+ fileCache[0] = fs.statSync(fileName).mtimeMs;
371
+ fileCache[1] = {};
372
+ fileCache[2] = {};
373
+ fileCache[3] = false;
374
+ shouldCheck = true;
375
+ }
240
376
  }
241
- if (shouldRetry) {
377
+ if (shouldCheck) {
242
378
  diagnostics = linter.lint(fileName, fileCache);
243
379
  }
244
380
  if (language) {
@@ -278,45 +414,6 @@ function lintAndFix(fileName, fileCache) {
278
414
  }
279
415
  return [diagnostics, fileCache];
280
416
  }
281
- function lint(fileName, fileCache) {
282
- let diagnostics = linter.lint(fileName, fileCache);
283
- if (language) {
284
- diagnostics = diagnostics
285
- .map(d => (0, transform_1.transformDiagnostic)(language, d, originalService.getCurrentProgram(), false))
286
- .filter(d => !!d);
287
- diagnostics = diagnostics.map(diagnostic => ({
288
- ...diagnostic,
289
- file: {
290
- fileName: diagnostic.file.fileName,
291
- text: getFileText(diagnostic.file.fileName),
292
- },
293
- relatedInformation: diagnostic.relatedInformation?.map(info => ({
294
- ...info,
295
- file: info.file ? {
296
- fileName: info.file.fileName,
297
- text: getFileText(info.file.fileName),
298
- } : undefined,
299
- })),
300
- }));
301
- }
302
- else {
303
- diagnostics = diagnostics.map(diagnostic => ({
304
- ...diagnostic,
305
- file: {
306
- fileName: diagnostic.file.fileName,
307
- text: diagnostic.file.text,
308
- },
309
- relatedInformation: diagnostic.relatedInformation?.map(info => ({
310
- ...info,
311
- file: info.file ? {
312
- fileName: info.file.fileName,
313
- text: info.file.text,
314
- } : undefined,
315
- })),
316
- }));
317
- }
318
- return [diagnostics, fileCache];
319
- }
320
417
  function getFileText(fileName) {
321
418
  return originalHost.getScriptSnapshot(fileName).getText(0, Number.MAX_VALUE);
322
419
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tsslint/cli",
3
- "version": "1.4.6",
3
+ "version": "1.5.1",
4
4
  "license": "MIT",
5
5
  "bin": {
6
6
  "tsslint": "./bin/tsslint.js"
@@ -16,11 +16,12 @@
16
16
  },
17
17
  "dependencies": {
18
18
  "@clack/prompts": "^0.8.2",
19
- "@tsslint/config": "1.4.6",
20
- "@tsslint/core": "1.4.6",
19
+ "@tsslint/config": "1.5.1",
20
+ "@tsslint/core": "1.5.1",
21
21
  "@volar/language-core": "~2.4.0",
22
22
  "@volar/typescript": "~2.4.0",
23
- "glob": "^10.4.1"
23
+ "glob": "^10.4.1",
24
+ "json5": "^2.2.3"
24
25
  },
25
26
  "peerDependencies": {
26
27
  "typescript": "*"
@@ -28,5 +29,5 @@
28
29
  "devDependencies": {
29
30
  "@vue/language-core": "latest"
30
31
  },
31
- "gitHead": "9c5bc3471bb1c737144f90a1a70470975088e7fc"
32
+ "gitHead": "a0c7b85e3ba032d5c9a92f69efbba51a9f6ce06e"
32
33
  }