Merge PR #3470: detect Dart and structurally identify Flutter dependencies

This commit is contained in:
haelyra committed 2026-10-10 17:36:59 -04:00
commit b40c9d5289
2 files changed
+115 -2

No files matched your search

+34 -2
View File
@@ -9,6 +9,7 @@
const fs = require('fs');
const path = require('path');
const yaml = require('js-yaml');
/**
* Language detection rules.
@@ -84,6 +85,11 @@ const LANGUAGE_RULES = [
type: 'php',
markers: ['composer.json', 'composer.lock'],
extensions: ['.php']
},
{
type: 'dart',
markers: ['pubspec.yaml', 'pubspec.lock'],
extensions: ['.dart']
}
];
@@ -129,7 +135,10 @@ const FRAMEWORK_RULES = [
{ framework: 'symfony', language: 'php', markers: ['symfony.lock'], packageKeys: ['symfony/framework-bundle'] },
// Elixir frameworks
{ framework: 'phoenix', language: 'elixir', markers: [], packageKeys: ['phoenix'] }
{ framework: 'phoenix', language: 'elixir', markers: [], packageKeys: ['phoenix'] },
// Dart frameworks
{ framework: 'flutter', language: 'dart', markers: [], packageKeys: [], customCheck: hasFlutterSdkDependency }
];
/**
@@ -330,6 +339,27 @@ function getElixirDeps(projectDir) {
}
}
/**
* Check whether pubspec.yaml declares the Flutter SDK as a dependency,
* which distinguishes a Flutter app from a plain Dart package.
* @param {string} projectDir - Project root directory
* @returns {boolean}
*/
function hasFlutterSdkDependency(projectDir) {
try {
const pubspecPath = path.join(projectDir, 'pubspec.yaml');
if (!fs.existsSync(pubspecPath)) return false;
const content = fs.readFileSync(pubspecPath, 'utf8');
const pubspec = yaml.load(content, { schema: yaml.JSON_SCHEMA });
// Only a declared Flutter SDK dependency identifies the framework.
// Unrelated configuration, comments, and strings must not count.
return pubspec?.dependencies?.flutter?.sdk === 'flutter'
|| pubspec?.dev_dependencies?.flutter?.sdk === 'flutter';
} catch {
return false;
}
}
/**
* Detect project languages and frameworks
* @param {string} [projectDir] - Project directory (defaults to cwd)
@@ -409,7 +439,9 @@ function detectProjectType(projectDir) {
});
}
if (hasMarker || hasDep) {
const hasCustom = typeof rule.customCheck === 'function' ? rule.customCheck(projectDir) : false;
if (hasMarker || hasDep || hasCustom) {
frameworks.push(rule.framework);
}
}
+81
View File
@@ -493,6 +493,87 @@ function runTests() {
}
})) passed++; else failed++;
// Dart/Flutter detection
console.log('\nDart/Flutter Detection:');
if (test('detects dart from pubspec.yaml and .dart files', () => {
const dir = createTempDir();
try {
writeTestFile(dir, 'pubspec.yaml', 'name: test_pkg\nversion: 1.0.0\n');
writeTestFile(dir, 'main.dart', 'void main() {}\n');
const result = detectProjectType(dir);
assert.ok(result.languages.includes('dart'));
} finally {
cleanupDir(dir);
}
})) passed++; else failed++;
if (test('detects flutter framework when pubspec.yaml depends on the Flutter SDK', () => {
const dir = createTempDir();
try {
writeTestFile(dir, 'pubspec.yaml', [
'name: halal_cosmetics_app',
'dependencies:',
' flutter:',
' sdk: flutter',
' cupertino_icons: ^1.0.2'
].join('\n'));
const result = detectProjectType(dir);
assert.ok(result.languages.includes('dart'));
assert.ok(result.frameworks.includes('flutter'), `flutter not detected: ${JSON.stringify(result.frameworks)}`);
assert.strictEqual(result.primary, 'flutter');
} finally {
cleanupDir(dir);
}
})) passed++; else failed++;
if (test('does not misclassify a plain Dart package as flutter', () => {
const dir = createTempDir();
try {
writeTestFile(dir, 'pubspec.yaml', [
'name: plain_dart_pkg',
'dependencies:',
' http: ^1.0.0'
].join('\n'));
const result = detectProjectType(dir);
assert.ok(result.languages.includes('dart'));
assert.ok(!result.frameworks.includes('flutter'), `plain Dart package wrongly detected as flutter: ${JSON.stringify(result.frameworks)}`);
assert.strictEqual(result.primary, 'dart');
} finally {
cleanupDir(dir);
}
})) passed++; else failed++;
if (test('does not combine unrelated Flutter and SDK configuration', () => {
const dir = createTempDir();
try {
writeTestFile(dir, 'pubspec.yaml', 'name: plain_dart\nflutter:\n assets: []\ndependencies:\n other:\n sdk: flutter\n');
assert.ok(!detectProjectType(dir).frameworks.includes('flutter'));
} finally {
cleanupDir(dir);
}
})) passed++; else failed++;
if (test('detects inline Flutter SDK dependencies', () => {
const dir = createTempDir();
try {
writeTestFile(dir, 'pubspec.yaml', 'name: flutter_pkg\ndependencies: {flutter: {sdk: flutter}}\n');
assert.ok(detectProjectType(dir).frameworks.includes('flutter'));
} finally {
cleanupDir(dir);
}
})) passed++; else failed++;
if (test('malformed pubspec does not claim Flutter detection', () => {
const dir = createTempDir();
try {
writeTestFile(dir, 'pubspec.yaml', 'dependencies: [\nflutter:\n sdk: flutter\n');
assert.ok(!detectProjectType(dir).frameworks.includes('flutter'));
} finally {
cleanupDir(dir);
}
})) passed++; else failed++;
// Edge cases
console.log('\nEdge Cases:');