@tsslint/cli 1.4.6 → 1.5.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/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)}`);
@@ -180,76 +169,60 @@ class Project {
180
169
  }
181
170
  }
182
171
  else {
172
+ const projectsFlag = process.argv.find(arg => arg.endsWith('-projects'));
173
+ if (projectsFlag) {
174
+ clack.log.warn(lightYellow(`Please use ${projectsFlag.slice(0, -1)} instead of ${projectsFlag} starting from version 1.5.0.`));
175
+ }
183
176
  const options = [
184
177
  {
185
- projectFlag: '--project',
186
- projectsFlag: '--projects',
178
+ projectFlags: ['--project', '--projects'],
187
179
  language: undefined,
188
180
  },
189
181
  {
190
- projectFlag: '--vue-project',
191
- projectsFlag: '--vue-projects',
182
+ projectFlags: ['--vue-project', '--vue-projects'],
192
183
  language: 'vue',
193
184
  },
194
185
  {
195
- projectFlag: '--mdx-project',
186
+ projectFlags: ['--mdx-project', '--mdx-projects'],
196
187
  projectsFlag: '--mdx-projects',
197
188
  language: 'mdx',
198
189
  },
199
190
  {
200
- projectFlag: '--astro-project',
201
- projectsFlag: '--astro-projects',
191
+ projectFlags: ['--astro-project', '--astro-projects'],
202
192
  language: 'astro',
203
193
  },
204
194
  ];
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, []);
195
+ for (const { projectFlags, language } of options) {
196
+ const projectFlag = projectFlags.find(flag => process.argv.includes(flag));
197
+ if (!projectFlag) {
198
+ continue;
199
+ }
200
+ let foundArg = false;
201
+ const projectsIndex = process.argv.indexOf(projectFlag);
202
+ for (let i = projectsIndex + 1; i < process.argv.length; i++) {
203
+ if (process.argv[i].startsWith('-')) {
204
+ break;
218
205
  }
219
- if (language) {
220
- tsconfigAndLanguages.get(tsconfig).push(language);
206
+ foundArg = true;
207
+ const searchGlob = process.argv[i];
208
+ const tsconfigs = glob.sync(searchGlob);
209
+ if (!tsconfigs.length) {
210
+ clack.log.error(lightRed(`No projects found for ${projectFlag} ${searchGlob}.`));
211
+ process.exit(1);
221
212
  }
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;
229
- }
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);
213
+ for (let tsconfig of tsconfigs) {
214
+ tsconfig = resolvePath(tsconfig);
215
+ if (!tsconfigAndLanguages.has(tsconfig)) {
216
+ tsconfigAndLanguages.set(tsconfig, []);
236
217
  }
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
- }
218
+ if (language) {
219
+ tsconfigAndLanguages.get(tsconfig).push(language);
247
220
  }
248
221
  }
249
- if (!foundArg) {
250
- clack.log.error(lightRed(`Missing argument for ${projectsFlag}.`));
251
- process.exit(1);
252
- }
222
+ }
223
+ if (!foundArg) {
224
+ clack.log.error(lightRed(`Missing argument for ${projectFlag}.`));
225
+ process.exit(1);
253
226
  }
254
227
  }
255
228
  }
@@ -278,9 +251,15 @@ class Project {
278
251
  return startWorker(worker.create());
279
252
  }));
280
253
  }
281
- spinner.stop(darkGray(cached
282
- ? `Processed ${processed} files with cache. (Use --force to ignore cache.)`
283
- : `Processed ${processed} files.`));
254
+ spinner.stop(cached
255
+ ? darkGray(`Processed ${processed} files with cache. (Use `) + cyan(`--force`) + darkGray(` to ignore cache.)`)
256
+ : darkGray(`Processed ${processed} files.`));
257
+ if (process.argv.includes('--fix') && !formattingSettings) {
258
+ const vscodeSettings = ts.findConfigFile(process.cwd(), ts.sys.fileExists, '.vscode/settings.json');
259
+ if (vscodeSettings) {
260
+ clack.log.message(darkGray(`Found available editor settings, you can use `) + cyan(`--vscode-settings ${path.relative(process.cwd(), vscodeSettings)}`) + darkGray(` to enable code format.`));
261
+ }
262
+ }
284
263
  const data = [
285
264
  [passed, 'passed', lightGreen],
286
265
  [errors, 'errors', lightRed],
@@ -292,7 +271,7 @@ class Project {
292
271
  .map(([count, label, color]) => color(`${count} ${label}`))
293
272
  .join(darkGray(' | '));
294
273
  if (hasFix) {
295
- summary += darkGray(` (Use --fix to apply automatic fixes.)`);
274
+ summary += darkGray(` (Use `) + cyan(`--fix`) + darkGray(` to apply automatic fixes.)`);
296
275
  }
297
276
  else if (errors || warnings) {
298
277
  summary += darkGray(` (No fixes available.)`);
@@ -315,7 +294,7 @@ class Project {
315
294
  })[0];
316
295
  }
317
296
  project.workers.push(linterWorker);
318
- const setupSuccess = await linterWorker.setup(project.tsconfig, project.languages, project.configFile, project.builtConfig, project.fileNames, project.options);
297
+ const setupSuccess = await linterWorker.setup(project.tsconfig, project.languages, project.configFile, project.builtConfig, project.fileNames, project.options, formattingSettings);
319
298
  if (!setupSuccess) {
320
299
  projects = projects.filter(p => p !== project);
321
300
  startWorker(linterWorker);
@@ -342,15 +321,9 @@ class Project {
342
321
  }
343
322
  }
344
323
  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);
324
+ project.cache[fileName] = fileCache = [fileMtime, {}, {}, false];
353
325
  }
326
+ let diagnostics = await linterWorker.lint(fileName, process.argv.includes('--fix'), fileCache);
354
327
  diagnostics = diagnostics.filter(diagnostic => diagnostic.category !== ts.DiagnosticCategory.Suggestion);
355
328
  if (diagnostics.length) {
356
329
  hasFix ||= await linterWorker.hasCodeFixes(fileName);
@@ -388,6 +361,60 @@ class Project {
388
361
  cache.saveCache(project.tsconfig, project.configFile, project.cache, ts.sys.createHash);
389
362
  await startWorker(linterWorker);
390
363
  }
364
+ function getFormattingSettings() {
365
+ let formattingSettings;
366
+ if (process.argv.includes('--vscode-settings')) {
367
+ formattingSettings = {
368
+ typescript: {},
369
+ javascript: {},
370
+ vue: {},
371
+ };
372
+ for (const section of ['typescript', 'javascript']) {
373
+ formattingSettings[section] = {
374
+ ...ts.getDefaultFormatCodeSettings('\n'),
375
+ indentStyle: ts.IndentStyle.Smart,
376
+ newLineCharacter: '\n',
377
+ insertSpaceAfterCommaDelimiter: true,
378
+ insertSpaceAfterConstructor: false,
379
+ insertSpaceAfterSemicolonInForStatements: true,
380
+ insertSpaceBeforeAndAfterBinaryOperators: true,
381
+ insertSpaceAfterKeywordsInControlFlowStatements: true,
382
+ insertSpaceAfterFunctionKeywordForAnonymousFunctions: true,
383
+ insertSpaceBeforeFunctionParenthesis: false,
384
+ insertSpaceAfterOpeningAndBeforeClosingNonemptyParenthesis: false,
385
+ insertSpaceAfterOpeningAndBeforeClosingNonemptyBrackets: false,
386
+ insertSpaceAfterOpeningAndBeforeClosingNonemptyBraces: true,
387
+ insertSpaceAfterOpeningAndBeforeClosingEmptyBraces: true,
388
+ insertSpaceAfterOpeningAndBeforeClosingTemplateStringBraces: false,
389
+ insertSpaceAfterOpeningAndBeforeClosingJsxExpressionBraces: false,
390
+ insertSpaceAfterTypeAssertion: false,
391
+ placeOpenBraceOnNewLineForFunctions: false,
392
+ placeOpenBraceOnNewLineForControlBlocks: false,
393
+ semicolons: ts.SemicolonPreference.Ignore,
394
+ };
395
+ }
396
+ let vscodeSettingsConfig = process.argv[process.argv.indexOf('--vscode-settings') + 1];
397
+ if (!vscodeSettingsConfig || vscodeSettingsConfig.startsWith('-')) {
398
+ clack.log.error(lightRed(`Missing argument for --vscode-settings.`));
399
+ process.exit(1);
400
+ }
401
+ const vscodeSettingsFile = resolvePath(vscodeSettingsConfig);
402
+ const vscodeSettings = (0, formatting_js_1.getVSCodeFormattingSettings)(vscodeSettingsFile);
403
+ formattingSettings.typescript = {
404
+ ...formattingSettings.typescript,
405
+ ...vscodeSettings.typescript,
406
+ };
407
+ formattingSettings.javascript = {
408
+ ...formattingSettings.javascript,
409
+ ...vscodeSettings.javascript,
410
+ };
411
+ formattingSettings.vue = {
412
+ ...formattingSettings.vue,
413
+ ...vscodeSettings.vue,
414
+ };
415
+ }
416
+ return formattingSettings;
417
+ }
391
418
  async function getBuiltConfig(configFile) {
392
419
  if (!builtConfigs.has(configFile)) {
393
420
  builtConfigs.set(configFile, core.buildConfig(configFile, ts.sys.createHash, spinner, (s, code) => log(darkGray(s), code)));
@@ -416,6 +443,20 @@ class Project {
416
443
  spinner = clack.spinner();
417
444
  spinner.start();
418
445
  }
446
+ function resolvePath(p) {
447
+ if (!path.isAbsolute(p)
448
+ && !p.startsWith('./')
449
+ && !p.startsWith('../')) {
450
+ p = `./${p}`;
451
+ }
452
+ try {
453
+ return require.resolve(p, { paths: [process.cwd()] });
454
+ }
455
+ catch {
456
+ clack.log.error(lightRed(`No such file: ${p}`));
457
+ process.exit(1);
458
+ }
459
+ }
419
460
  })();
420
461
  async function parseCommonLine(tsconfig, languages) {
421
462
  const jsonConfigFile = ts.readJsonConfigFile(tsconfig, ts.sys.readFile);
@@ -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
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.0",
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.0",
20
+ "@tsslint/core": "1.5.0",
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": "6585740b4ecbc8251163923b4e8c7978fe9cb12c"
32
33
  }