Skip to content

Commit 6a322c1

Browse files
author
Jonas Greifenhain
committed
Check license header in flutter analyze
1 parent 89629c4 commit 6a322c1

File tree

7 files changed

+264
-0
lines changed

7 files changed

+264
-0
lines changed
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
/// Custom lint rules for Amplify Flutter packages.
5+
///
6+
/// This library is the entry point for the custom_lint plugin.
7+
/// It is automatically discovered by the `custom_lint` package.
8+
library amplify_lints;
9+
10+
export 'src/lints/missing_license_header.dart' show MissingLicenseHeader;
11+
export 'src/plugin.dart' show createPlugin;

packages/amplify_lints/lib/app.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
include: package:flutter_lints/flutter.yaml
22

33
analyzer:
4+
plugins:
5+
- custom_lint
46
language:
57
strict-casts: true
68
strict-inference: true

packages/amplify_lints/lib/library.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
include: package:lints/recommended.yaml
22

33
analyzer:
4+
plugins:
5+
- custom_lint
46
language:
57
strict-casts: true
68
strict-inference: true
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
import 'dart:io';
5+
6+
import 'package:analyzer/error/error.dart' show AnalysisError, ErrorSeverity;
7+
import 'package:analyzer/error/listener.dart';
8+
import 'package:custom_lint_builder/custom_lint_builder.dart';
9+
10+
/// The expected license header lines.
11+
const _licenseHeader = [
12+
'// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.',
13+
'// SPDX-License-Identifier: Apache-2.0',
14+
];
15+
16+
/// A custom lint rule that checks for the presence of the standard
17+
/// Amazon/Apache-2.0 license header at the top of every Dart file.
18+
///
19+
/// **Good:**
20+
/// ```dart
21+
/// // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
22+
/// // SPDX-License-Identifier: Apache-2.0
23+
///
24+
/// library my_library;
25+
/// ```
26+
///
27+
/// **Bad:**
28+
/// ```dart
29+
/// library my_library;
30+
/// ```
31+
class MissingLicenseHeader extends DartLintRule {
32+
/// Creates a new [MissingLicenseHeader] lint rule.
33+
const MissingLicenseHeader() : super(code: _code);
34+
35+
static const _code = LintCode(
36+
name: 'missing_license_header',
37+
problemMessage:
38+
'Dart files must start with the Amazon copyright and Apache-2.0 '
39+
'license header.',
40+
correctionMessage: 'Add the license header to the top of this file.',
41+
errorSeverity: ErrorSeverity.WARNING,
42+
);
43+
44+
@override
45+
void run(
46+
CustomLintResolver resolver,
47+
ErrorReporter reporter,
48+
CustomLintContext context,
49+
) {
50+
context.registry.addCompilationUnit((node) {
51+
final path = resolver.path;
52+
53+
// Read the raw file content to check for comments (which are not
54+
// present in the AST returned by toSource()).
55+
final file = File(path);
56+
if (!file.existsSync()) return;
57+
58+
final content = file.readAsStringSync();
59+
if (_hasLicenseHeader(content)) return;
60+
61+
// Report the lint on the first token in the file.
62+
final firstToken = node.beginToken;
63+
reporter.atToken(firstToken, _code);
64+
});
65+
}
66+
67+
@override
68+
List<Fix> getFixes() => [_AddLicenseHeaderFix()];
69+
}
70+
71+
/// Returns `true` if [content] starts with the expected license header.
72+
bool _hasLicenseHeader(String content) {
73+
final lines = content.split('\n');
74+
if (lines.length < _licenseHeader.length) return false;
75+
76+
for (var i = 0; i < _licenseHeader.length; i++) {
77+
if (lines[i].trimRight() != _licenseHeader[i]) return false;
78+
}
79+
return true;
80+
}
81+
82+
class _AddLicenseHeaderFix extends DartFix {
83+
@override
84+
void run(
85+
CustomLintResolver resolver,
86+
ChangeReporter reporter,
87+
CustomLintContext context,
88+
AnalysisError analysisError,
89+
List<AnalysisError> others,
90+
) {
91+
context.registry.addCompilationUnit((node) {
92+
if (!analysisError.sourceRange.intersects(node.beginToken.sourceRange)) {
93+
return;
94+
}
95+
96+
final changeBuilder = reporter.createChangeBuilder(
97+
message: 'Add license header',
98+
priority: 100,
99+
);
100+
101+
changeBuilder.addDartFileEdit((builder) {
102+
builder.addSimpleInsertion(
103+
0,
104+
'${_licenseHeader.join('\n')}\n\n',
105+
);
106+
});
107+
});
108+
}
109+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
import 'package:amplify_lints/src/lints/missing_license_header.dart';
5+
import 'package:custom_lint_builder/custom_lint_builder.dart';
6+
7+
/// Creates the amplify_lints custom lint plugin.
8+
PluginBase createPlugin() => _AmplifyLints();
9+
10+
class _AmplifyLints extends PluginBase {
11+
@override
12+
List<LintRule> getLintRules(CustomLintConfigs configs) => [
13+
const MissingLicenseHeader(),
14+
];
15+
}

packages/amplify_lints/pubspec.yaml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,5 +9,10 @@ environment:
99
sdk: ^3.9.0
1010

1111
dependencies:
12+
analyzer: ">=6.0.0 <8.0.0"
13+
custom_lint_builder: ^0.7.0
1214
flutter_lints: ^6.0.0
1315
lints: ^6.0.0
16+
17+
dev_dependencies:
18+
test: ^1.24.0
Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
import 'dart:io';
5+
6+
import 'package:amplify_lints/src/lints/missing_license_header.dart';
7+
import 'package:test/test.dart';
8+
9+
void main() {
10+
late Directory tmpDir;
11+
12+
setUp(() {
13+
tmpDir = Directory.systemTemp.createTempSync('license_header_test_');
14+
});
15+
16+
tearDown(() {
17+
tmpDir.deleteSync(recursive: true);
18+
});
19+
20+
File _createDartFile(String name, String content) {
21+
final file = File('${tmpDir.path}/$name');
22+
file.writeAsStringSync(content);
23+
return file;
24+
}
25+
26+
group('MissingLicenseHeader', () {
27+
const lint = MissingLicenseHeader();
28+
29+
test('reports lint when license header is missing', () async {
30+
final file = _createDartFile(
31+
'no_header.dart',
32+
'void main() {}\n',
33+
);
34+
35+
final errors = await lint.testAnalyzeAndRun(file);
36+
expect(errors, hasLength(1));
37+
expect(errors.first.errorCode.name, 'missing_license_header');
38+
});
39+
40+
test('does not report lint when license header is present', () async {
41+
final file = _createDartFile(
42+
'with_header.dart',
43+
'// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n'
44+
'// SPDX-License-Identifier: Apache-2.0\n'
45+
'\n'
46+
'void main() {}\n',
47+
);
48+
49+
final errors = await lint.testAnalyzeAndRun(file);
50+
expect(errors, isEmpty);
51+
});
52+
53+
test('reports lint when only copyright line is present', () async {
54+
final file = _createDartFile(
55+
'partial_header.dart',
56+
'// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n'
57+
'\n'
58+
'void main() {}\n',
59+
);
60+
61+
final errors = await lint.testAnalyzeAndRun(file);
62+
expect(errors, hasLength(1));
63+
});
64+
65+
test('reports lint when only SPDX line is present', () async {
66+
final file = _createDartFile(
67+
'spdx_only.dart',
68+
'// SPDX-License-Identifier: Apache-2.0\n'
69+
'\n'
70+
'void main() {}\n',
71+
);
72+
73+
final errors = await lint.testAnalyzeAndRun(file);
74+
expect(errors, hasLength(1));
75+
});
76+
77+
test('reports lint when header lines are in wrong order', () async {
78+
final file = _createDartFile(
79+
'wrong_order.dart',
80+
'// SPDX-License-Identifier: Apache-2.0\n'
81+
'// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n'
82+
'\n'
83+
'void main() {}\n',
84+
);
85+
86+
final errors = await lint.testAnalyzeAndRun(file);
87+
expect(errors, hasLength(1));
88+
});
89+
90+
test('allows content after the header', () async {
91+
final file = _createDartFile(
92+
'full_file.dart',
93+
'// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n'
94+
'// SPDX-License-Identifier: Apache-2.0\n'
95+
'\n'
96+
"import 'dart:core';\n"
97+
'\n'
98+
'class Foo {\n'
99+
' void bar() {}\n'
100+
'}\n',
101+
);
102+
103+
final errors = await lint.testAnalyzeAndRun(file);
104+
expect(errors, isEmpty);
105+
});
106+
107+
test('does not report lint when header has trailing whitespace', () async {
108+
final file = _createDartFile(
109+
'trailing_ws.dart',
110+
'// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. \n'
111+
'// SPDX-License-Identifier: Apache-2.0 \n'
112+
'\n'
113+
'void main() {}\n',
114+
);
115+
116+
final errors = await lint.testAnalyzeAndRun(file);
117+
expect(errors, isEmpty);
118+
});
119+
});
120+
}

0 commit comments

Comments
 (0)