blob: f831a1ee71b7cd27a82638f6c64ef3b1ad40de30 [file]
// Copyright (c) 2014, 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:typed_data';
import 'package:_fe_analyzer_shared/src/parser/experimental_features.dart';
import 'package:_fe_analyzer_shared/src/scanner/scanner.dart' as fasta;
import 'package:_fe_analyzer_shared/src/scanner/token.dart' show Token;
import 'package:analyzer/dart/analysis/features.dart';
import 'package:analyzer/src/dart/analysis/experiments.dart';
import 'package:analyzer/src/diagnostic/diagnostic.dart' as diag;
import 'package:meta/meta.dart';
import 'package:pub_semver/pub_semver.dart';
/// The class `Scanner` implements a scanner for Dart code.
///
/// The lexical structure of Dart is ambiguous without knowledge of the context
/// in which a token is being scanned. For example, without context we cannot
/// determine whether source of the form "<<" should be scanned as a single
/// left-shift operator or as two left angle brackets. This scanner does not
/// have any context, so it always resolves such conflicts by scanning the
/// longest possible token.
class Scanner {
static final Uint8List _lineStartsZero = Uint8List(0);
/// Allows to - by default - either preserve or not preserve comments while
/// testing.
@visibleForTesting
static bool preserveCommentsDefaultForTesting = true;
/// The input text to be scanned.
final String _inputText;
/// The callback to report diagnostics.
final void Function(diag.LocatedDiagnostic) reportError;
/// If the file has [fasta.LanguageVersionToken], it is allowed to use the
/// language version greater than the one specified in the package config.
/// So, we need to know the full feature set for the context.
late final FeatureSet _featureSetForOverriding;
/// The flag specifying whether documentation comments should be parsed.
bool _preserveComments = preserveCommentsDefaultForTesting;
List<int>? _lineStarts;
Version? _overrideVersion;
late FeatureSet _featureSet;
/// Initializes a scanner to scan the given [inputText].
///
/// The [reportError] callback will be informed of any errors that are found.
Scanner({required String inputText, required this.reportError})
: _inputText = inputText;
/// The features associated with this scanner.
///
/// If a language version comment (e.g. '// @dart = 2.3') is detected
/// when calling [tokenize] and this field is non-null, then this field
/// will be updated to contain a downgraded feature set based upon the
/// language version specified.
///
/// Use [configureFeatures] to set the features.
FeatureSet get featureSet => _featureSet;
List<int> get lineStarts => _lineStarts ?? _lineStartsZero;
/// The language version override specified for this compilation unit using a
/// token like '// @dart = 2.7', or `null` if no override is specified.
Version? get overrideVersion => _overrideVersion;
set preserveComments(bool preserveComments) {
_preserveComments = preserveComments;
}
/// Configures the scanner appropriately for the given [featureSet].
void configureFeatures({
required FeatureSet featureSetForOverriding,
required FeatureSet featureSet,
}) {
_featureSetForOverriding = featureSetForOverriding;
_featureSet = featureSet;
}
Token tokenize() {
fasta.ScannerResult result = fasta.scanString(
_inputText,
configuration: ExperimentalFeaturesStatus(
_featureSet,
).buildScannerConfiguration(),
includeComments: _preserveComments,
languageVersionChanged: _languageVersionChanged,
);
// fasta pretends there is an additional line at EOF so we skip the last one.
_lineStarts = result.lineStarts.copyToTypedDataWithoutLastElement();
return result.tokens;
}
void _languageVersionChanged(
fasta.Scanner scanner,
fasta.LanguageVersionToken versionToken,
) {
var overrideMajor = versionToken.major;
var overrideMinor = versionToken.minor;
if (overrideMajor < 0 || overrideMinor < 0) {
return;
}
var overrideVersion = Version(overrideMajor, overrideMinor, 0);
_overrideVersion = overrideVersion;
var latestVersion = ExperimentStatus.currentVersion;
if (overrideVersion > latestVersion) {
reportError(
diag.invalidLanguageVersionOverrideGreater
.withArguments(
latestMajor: latestVersion.major,
latestMinor: latestVersion.minor,
)
.atOffset(offset: versionToken.offset, length: versionToken.length),
);
_overrideVersion = null;
} else {
_featureSet = _featureSetForOverriding.restrictToVersion(overrideVersion);
scanner.configuration = ExperimentalFeaturesStatus(
_featureSet,
).buildScannerConfiguration();
}
}
}