| // Copyright (c) 2024, the Dart project authors. Please see the AUTHORS file |
| // for details. All rights reserved. Use of this source code is governed by a |
| // BSD-style license that can be found in the LICENSE file. |
| |
| import 'dart:convert'; |
| |
| import 'package:path/path.dart' as p; |
| import 'package:test/test.dart'; |
| import 'package:test_descriptor/test_descriptor.dart' as d; |
| |
| import '../utils.dart'; |
| |
| void main() { |
| compileFormatter(); |
| |
| group('given file paths', () { |
| test('formats a directory', () async { |
| await d.dir('code', [ |
| d.file('a.dart', unformattedSource), |
| d.file('b.dart', formattedSource), |
| d.file('c.dart', unformattedSource), |
| ]).create(); |
| |
| var process = await runFormatterOnDir(); |
| await expectLater( |
| process.stdout, |
| emitsInOrder([ |
| 'Formatted ${p.join('code', 'a.dart')}', |
| 'Formatted ${p.join('code', 'c.dart')}', |
| ]), |
| ); |
| await expectLater( |
| process.stdout, |
| emits(startsWith('Formatted 3 files (2 changed)')), |
| ); |
| await process.shouldExit(0); |
| |
| // Overwrites the files. |
| await d.dir('code', [d.file('a.dart', formattedSource)]).validate(); |
| await d.dir('code', [d.file('c.dart', formattedSource)]).validate(); |
| }); |
| |
| test('formats multiple paths', () async { |
| await d.dir('code', [ |
| d.dir('subdir', [d.file('a.dart', unformattedSource)]), |
| d.file('b.dart', unformattedSource), |
| d.file('c.dart', unformattedSource), |
| ]).create(); |
| |
| var process = await runFormatter([ |
| p.join('code', 'subdir'), |
| p.join('code', 'c.dart'), |
| ]); |
| await expectLater( |
| process.stdout, |
| emitsInOrder([ |
| 'Formatted ${p.join('code', 'subdir', 'a.dart')}', |
| 'Formatted ${p.join('code', 'c.dart')}', |
| ]), |
| ); |
| await expectLater( |
| process.stdout, |
| emits(startsWith('Formatted 2 files (2 changed)')), |
| ); |
| await process.shouldExit(0); |
| |
| // Overwrites the selected files. |
| await d.dir('code', [ |
| d.dir('subdir', [d.file('a.dart', formattedSource)]), |
| d.file('b.dart', unformattedSource), |
| d.file('c.dart', formattedSource), |
| ]).validate(); |
| }); |
| }); |
| |
| test('exits with 64 on a command line argument error', () async { |
| var process = await runFormatter(['-wat']); |
| await process.shouldExit(64); |
| }); |
| |
| test('exits with 65 on a parse error', () async { |
| await d.dir('code', [d.file('a.dart', 'herp derp i are a dart')]).create(); |
| |
| var process = await runFormatterOnDir(); |
| await process.shouldExit(65); |
| }); |
| |
| test('--version prints the version number', () async { |
| var process = await runFormatter(['--version']); |
| |
| // Match something roughly semver-like. |
| await expectLater( |
| process.stdout, |
| emits(matches(RegExp(r'\d+\.\d+\.\d+.*'))), |
| ); |
| await process.shouldExit(0); |
| }); |
| |
| group('--help', () { |
| test('non-verbose shows description and common options', () async { |
| var process = await runFormatter(['--help']); |
| await expectLater( |
| process.stdout, |
| emits('Idiomatically format Dart source code.'), |
| ); |
| await expectLater(process.stdout, emitsThrough(contains('-o, --output'))); |
| await expectLater(process.stdout, neverEmits(contains('--summary'))); |
| await process.shouldExit(0); |
| }); |
| |
| test('verbose shows description and all options', () async { |
| var process = await runFormatter(['--help', '--verbose']); |
| await expectLater( |
| process.stdout, |
| emits('Idiomatically format Dart source code.'), |
| ); |
| await expectLater(process.stdout, emitsThrough(contains('-o, --output'))); |
| await expectLater(process.stdout, emitsThrough(contains('--show'))); |
| await expectLater(process.stdout, emitsThrough(contains('--summary'))); |
| await process.shouldExit(0); |
| }); |
| }); |
| |
| test('--verbose errors if not used with --help', () async { |
| var process = await runFormatterOnDir(['--verbose']); |
| expect(await process.stderr.next, 'Can only use --verbose with --help.'); |
| await process.shouldExit(64); |
| }); |
| |
| group('--indent', () { |
| test('sets the leading indentation of the output', () async { |
| var process = await runFormatter(['--indent=3']); |
| process.stdin.writeln("main() {'''"); |
| process.stdin.writeln("a flush left multi-line string''';}"); |
| await process.stdin.close(); |
| |
| await expectLater( |
| process.stdout, |
| emitsInOrder([ |
| ' main() {', |
| " '''", |
| "a flush left multi-line string''';", |
| ' }', |
| ]), |
| ); |
| await process.shouldExit(0); |
| }); |
| |
| test('errors if the indent is not a non-negative number', () async { |
| var process = await runFormatter(['--indent=notanum']); |
| await process.shouldExit(64); |
| |
| process = await runFormatter(['--indent=-4']); |
| await process.shouldExit(64); |
| }); |
| }); |
| |
| group('--set-exit-if-changed', () { |
| test('gives exit code 0 if there are no changes', () async { |
| await d.dir('code', [d.file('a.dart', formattedSource)]).create(); |
| |
| var process = await runFormatterOnDir(['--set-exit-if-changed']); |
| await process.shouldExit(0); |
| }); |
| |
| test('gives exit code 1 if there are changes', () async { |
| await d.dir('code', [d.file('a.dart', unformattedSource)]).create(); |
| |
| var process = await runFormatterOnDir(['--set-exit-if-changed']); |
| await process.shouldExit(1); |
| }); |
| |
| test('gives exit code 1 if there are changes when not writing', () async { |
| await d.dir('code', [d.file('a.dart', unformattedSource)]).create(); |
| |
| var process = await runFormatterOnDir([ |
| '--set-exit-if-changed', |
| '--show=none', |
| ]); |
| await process.shouldExit(1); |
| }); |
| }); |
| |
| group('--selection', () { |
| test('errors if given path', () async { |
| var process = await runFormatter(['--selection', 'path']); |
| await process.shouldExit(64); |
| }); |
| |
| test('errors on wrong number of components', () async { |
| var process = await runFormatter(['--selection', '1']); |
| await process.shouldExit(64); |
| |
| process = await runFormatter(['--selection', '1:2:3']); |
| await process.shouldExit(64); |
| }); |
| |
| test('errors on non-integer component', () async { |
| var process = await runFormatter(['--selection', '1:2.3']); |
| await process.shouldExit(64); |
| }); |
| |
| test('updates selection', () async { |
| var process = await runFormatter(['--output=json', '--selection=6:10']); |
| process.stdin.writeln(unformattedSource); |
| await process.stdin.close(); |
| |
| var json = jsonEncode({ |
| 'path': 'stdin', |
| 'source': formattedSource, |
| 'selection': {'offset': 5, 'length': 9}, |
| }); |
| |
| await expectLater(process.stdout, emits(json)); |
| await process.shouldExit(); |
| }); |
| }); |
| |
| group('--enable-experiment', () { |
| test('passes experiment flags to parser', () async { |
| var process = await runFormatter([ |
| '--enable-experiment=test-experiment,variance', |
| ]); |
| process.stdin.writeln('class Writer<in T> {}'); |
| await process.stdin.close(); |
| |
| // The formatter doesn't actually support formatting variance annotations, |
| // but we want to test that the experiment flags are passed all the way |
| // to the parser, so just test that it parses the variance annotation |
| // without errors and then fails to format. |
| expect( |
| await process.stderr.next, |
| 'Hit a bug in the formatter when formatting stdin.', |
| ); |
| expect( |
| await process.stderr.next, |
| 'Please report at: github.com/dart-lang/dart_style/issues', |
| ); |
| expect( |
| await process.stderr.next, |
| 'The formatter produced unexpected output. Input was:', |
| ); |
| expect(await process.stderr.next, 'class Writer<in T> {}'); |
| expect(await process.stderr.next, ''); |
| expect(await process.stderr.next, 'Which formatted to:'); |
| expect(await process.stderr.next, 'class Writer<T> {}'); |
| await process.shouldExit(70); |
| }); |
| }); |
| } |