//! Edition detection utilities. //! //! This module contains the [`SpecEdition`] struct, which is used in the tester to //! classify all tests per minimum required ECMAScript edition. use std::fmt::Display; use serde_repr::{Deserialize_repr, Serialize_repr}; use crate::read::{MetaData, TestFlag}; /// Minimum edition required by a specific feature in the `test262` repository. static FEATURE_EDITION: phf::Map<&'static str, SpecEdition> = phf::phf_map! { // Proposed language features // Intl.Locale Info // https://github.com/tc39/proposal-intl-locale-info "Intl.Locale-info" => SpecEdition::ESNext, // FinalizationRegistry#cleanupSome // https://github.com/tc39/proposal-cleanup-some "FinalizationRegistry.prototype.cleanupSome" => SpecEdition::ESNext, // Intl.NumberFormat V3 // https://github.com/tc39/proposal-intl-numberformat-v3 "Intl.NumberFormat-v3" => SpecEdition::ESNext, // Legacy RegExp features // https://github.com/tc39/proposal-regexp-legacy-features "legacy-regexp" => SpecEdition::ESNext, // Import Attributes // https://github.com/tc39/proposal-import-attributes/ "import-attributes" => SpecEdition::ESNext, // Import Assertions // https://github.com/tc39/proposal-import-assertions/ "import-assertions" => SpecEdition::ESNext, // JSON modules // https://github.com/tc39/proposal-json-modules "json-modules" => SpecEdition::ESNext, // ArrayBuffer transfer // https://github.com/tc39/proposal-arraybuffer-transfer "arraybuffer-transfer" => SpecEdition::ESNext, // Temporal // https://github.com/tc39/proposal-temporal "Temporal" => SpecEdition::ESNext, // ShadowRealm, née Callable Boundary Realms // https://github.com/tc39/proposal-realms "ShadowRealm" => SpecEdition::ESNext, // Intl.DurationFormat // https://github.com/tc39/proposal-intl-duration-format "Intl.DurationFormat" => SpecEdition::ESNext, // Decorators // https://github.com/tc39/proposal-decorators "decorators" => SpecEdition::ESNext, // Duplicate named capturing groups // https://github.com/tc39/proposal-duplicate-named-capturing-groups "regexp-duplicate-named-groups" => SpecEdition::ESNext, // Array.fromAsync // https://github.com/tc39/proposal-array-from-async "Array.fromAsync" => SpecEdition::ESNext, // JSON.parse with source // https://github.com/tc39/proposal-json-parse-with-source "json-parse-with-source" => SpecEdition::ESNext, // Iterator Helpers // https://github.com/tc39/proposal-iterator-helpers "iterator-helpers" => SpecEdition::ESNext, // Set methods // https://github.com/tc39/proposal-set-methods "set-methods" => SpecEdition::ESNext, // Part of the next ES15 edition "Atomics.waitAsync" => SpecEdition::ESNext, "regexp-v-flag" => SpecEdition::ESNext, "String.prototype.isWellFormed" => SpecEdition::ESNext, "String.prototype.toWellFormed" => SpecEdition::ESNext, "resizable-arraybuffer" => SpecEdition::ESNext, "promise-with-resolvers" => SpecEdition::ESNext, "array-grouping" => SpecEdition::ESNext, // Standard language features "AggregateError" => SpecEdition::ES12, "align-detached-buffer-semantics-with-web-reality" => SpecEdition::ES12, "arbitrary-module-namespace-names" => SpecEdition::ES13, "ArrayBuffer" => SpecEdition::ES6, "array-find-from-last" => SpecEdition::ES14, "Array.prototype.at" => SpecEdition::ES13, "Array.prototype.flat" => SpecEdition::ES10, "Array.prototype.flatMap" => SpecEdition::ES10, "Array.prototype.includes" => SpecEdition::ES7, "Array.prototype.values" => SpecEdition::ES6, "arrow-function" => SpecEdition::ES6, "async-iteration" => SpecEdition::ES9, "async-functions" => SpecEdition::ES8, "Atomics" => SpecEdition::ES8, "BigInt" => SpecEdition::ES11, "caller" => SpecEdition::ES5, "change-array-by-copy" => SpecEdition::ES14, "class" => SpecEdition::ES6, "class-fields-private" => SpecEdition::ES13, "class-fields-private-in" => SpecEdition::ES13, "class-fields-public" => SpecEdition::ES13, "class-methods-private" => SpecEdition::ES13, "class-static-block" => SpecEdition::ES13, "class-static-fields-private" => SpecEdition::ES13, "class-static-fields-public" => SpecEdition::ES13, "class-static-methods-private" => SpecEdition::ES13, "coalesce-expression" => SpecEdition::ES11, "computed-property-names" => SpecEdition::ES6, "const" => SpecEdition::ES6, "cross-realm" => SpecEdition::ES6, "DataView" => SpecEdition::ES6, "DataView.prototype.getFloat32" => SpecEdition::ES6, "DataView.prototype.getFloat64" => SpecEdition::ES6, "DataView.prototype.getInt16" => SpecEdition::ES6, "DataView.prototype.getInt32" => SpecEdition::ES6, "DataView.prototype.getInt8" => SpecEdition::ES6, "DataView.prototype.getUint16" => SpecEdition::ES6, "DataView.prototype.getUint32" => SpecEdition::ES6, "DataView.prototype.setUint8" => SpecEdition::ES6, "default-parameters" => SpecEdition::ES6, "destructuring-assignment" => SpecEdition::ES6, "destructuring-binding" => SpecEdition::ES6, "dynamic-import" => SpecEdition::ES11, "error-cause" => SpecEdition::ES13, "exponentiation" => SpecEdition::ES7, "export-star-as-namespace-from-module" => SpecEdition::ES11, "FinalizationRegistry" => SpecEdition::ES12, "for-in-order" => SpecEdition::ES11, "for-of" => SpecEdition::ES6, "Float32Array" => SpecEdition::ES6, "Float64Array" => SpecEdition::ES6, "generators" => SpecEdition::ES6, "globalThis" => SpecEdition::ES11, "hashbang" => SpecEdition::ES14, "import.meta" => SpecEdition::ES11, "Int8Array" => SpecEdition::ES6, "Int16Array" => SpecEdition::ES6, "Int32Array" => SpecEdition::ES6, "Intl-enumeration" => SpecEdition::ES14, "intl-normative-optional" => SpecEdition::ES8, "Intl.DateTimeFormat-datetimestyle" => SpecEdition::ES12, "Intl.DateTimeFormat-dayPeriod" => SpecEdition::ES8, "Intl.DateTimeFormat-extend-timezonename" => SpecEdition::ES13, "Intl.DateTimeFormat-formatRange" => SpecEdition::ES12, "Intl.DateTimeFormat-fractionalSecondDigits" => SpecEdition::ES12, "Intl.DisplayNames" => SpecEdition::ES12, "Intl.DisplayNames-v2" => SpecEdition::ES13, "Intl.ListFormat" => SpecEdition::ES12, "Intl.Locale" => SpecEdition::ES12, "Intl.NumberFormat-unified" => SpecEdition::ES11, "Intl.RelativeTimeFormat" => SpecEdition::ES11, "Intl.Segmenter" => SpecEdition::ES13, "json-superset" => SpecEdition::ES10, "let" => SpecEdition::ES6, "logical-assignment-operators" => SpecEdition::ES12, "Map" => SpecEdition::ES6, "new.target" => SpecEdition::ES6, "numeric-separator-literal" => SpecEdition::ES12, "object-rest" => SpecEdition::ES9, "object-spread" => SpecEdition::ES9, "Object.fromEntries" => SpecEdition::ES10, "Object.hasOwn" => SpecEdition::ES13, "Object.is" => SpecEdition::ES6, "optional-catch-binding" => SpecEdition::ES10, "optional-chaining" => SpecEdition::ES11, "Promise" => SpecEdition::ES6, "Promise.allSettled" => SpecEdition::ES11, "Promise.any" => SpecEdition::ES12, "Promise.prototype.finally" => SpecEdition::ES9, "Proxy" => SpecEdition::ES6, "proxy-missing-checks" => SpecEdition::ES6, "Reflect" => SpecEdition::ES6, "Reflect.construct" => SpecEdition::ES6, "Reflect.set" => SpecEdition::ES6, "Reflect.setPrototypeOf" => SpecEdition::ES6, "regexp-dotall" => SpecEdition::ES9, "regexp-lookbehind" => SpecEdition::ES9, "regexp-match-indices" => SpecEdition::ES13, "regexp-named-groups" => SpecEdition::ES9, "regexp-unicode-property-escapes" => SpecEdition::ES9, "rest-parameters" => SpecEdition::ES6, "Set" => SpecEdition::ES6, "SharedArrayBuffer" => SpecEdition::ES8, "string-trimming" => SpecEdition::ES10, "String.fromCodePoint" => SpecEdition::ES6, "String.prototype.at" => SpecEdition::ES13, "String.prototype.endsWith" => SpecEdition::ES6, "String.prototype.includes" => SpecEdition::ES6, "String.prototype.matchAll" => SpecEdition::ES11, "String.prototype.replaceAll" => SpecEdition::ES12, "String.prototype.trimEnd" => SpecEdition::ES10, "String.prototype.trimStart" => SpecEdition::ES10, "super" => SpecEdition::ES6, "Symbol" => SpecEdition::ES6, "symbols-as-weakmap-keys" => SpecEdition::ES14, "Symbol.asyncIterator" => SpecEdition::ES9, "Symbol.hasInstance" => SpecEdition::ES6, "Symbol.isConcatSpreadable" => SpecEdition::ES6, "Symbol.iterator" => SpecEdition::ES6, "Symbol.match" => SpecEdition::ES6, "Symbol.matchAll" => SpecEdition::ES11, "Symbol.prototype.description" => SpecEdition::ES10, "Symbol.replace" => SpecEdition::ES6, "Symbol.search" => SpecEdition::ES6, "Symbol.species" => SpecEdition::ES6, "Symbol.split" => SpecEdition::ES6, "Symbol.toPrimitive" => SpecEdition::ES6, "Symbol.toStringTag" => SpecEdition::ES6, "Symbol.unscopables" => SpecEdition::ES6, "tail-call-optimization" => SpecEdition::ES6, "template" => SpecEdition::ES6, "top-level-await" => SpecEdition::ES13, "TypedArray" => SpecEdition::ES6, "TypedArray.prototype.at" => SpecEdition::ES13, "u180e" => SpecEdition::ES7, "Uint8Array" => SpecEdition::ES6, "Uint16Array" => SpecEdition::ES6, "Uint32Array" => SpecEdition::ES6, "Uint8ClampedArray" => SpecEdition::ES6, "WeakMap" => SpecEdition::ES6, "WeakRef" => SpecEdition::ES12, "WeakSet" => SpecEdition::ES6, "well-formed-json-stringify" => SpecEdition::ES10, "__proto__" => SpecEdition::ES6, "__getter__" => SpecEdition::ES8, "__setter__" => SpecEdition::ES8, // Test-Harness Features "IsHTMLDDA" => SpecEdition::ES9, "host-gc-required" => SpecEdition::ES5, }; /// List of ECMAScript editions that can be tested in the `test262` repository. #[derive( Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Default, Serialize_repr, Deserialize_repr, clap::ValueEnum, )] #[repr(u8)] pub(crate) enum SpecEdition { /// ECMAScript 5.1 Edition /// /// ES5 = 5, /// ECMAScript 6th Edition /// /// ES6, /// ECMAScript 7th Edition /// /// ES7, /// ECMAScript 8th Edition /// /// ES8, /// ECMAScript 9th Edition /// /// ES9, /// ECMAScript 10th Edition /// /// ES10, /// ECMAScript 11th Edition /// /// ES11, /// ECMAScript 12th Edition /// /// ES12, /// ECMAScript 13th Edition /// /// ES13, /// ECMAScript 14th Edition /// /// ES14, /// The edition being worked on right now. /// /// A draft is currently available [here](https://tc39.es/ecma262). #[default] ESNext = 255, } impl Display for SpecEdition { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match *self { Self::ESNext => write!(f, "ECMAScript Next"), Self::ES5 => write!(f, "ECMAScript 5.1"), v => write!(f, "ECMAScript {}", v as u8), } } } impl SpecEdition { /// Gets the minimum required ECMAScript edition of a test from its metadata. /// /// If the function finds unknown features in `metadata`, returns an `Err(Vec<&str>)` containing /// the list of unknown features. pub(crate) fn from_test_metadata(metadata: &MetaData) -> Result> { let mut min_edition = if metadata.flags.contains(&TestFlag::Async) { Self::ES8 } else if metadata.flags.contains(&TestFlag::Module) || metadata.esid.is_some() || metadata.es6id.is_some() { Self::ES6 } else { Self::ES5 }; let mut unknowns = Vec::new(); for feature in &*metadata.features { let Some(feature_edition) = FEATURE_EDITION.get(feature).copied() else { unknowns.push(&**feature); continue; }; min_edition = std::cmp::max(min_edition, feature_edition); } if unknowns.is_empty() { Ok(min_edition) } else { Err(unknowns) } } /// Gets an iterator of all currently available editions. pub(crate) fn all_editions() -> impl Iterator { [ Self::ES5, Self::ES6, Self::ES7, Self::ES8, Self::ES9, Self::ES10, Self::ES11, Self::ES12, Self::ES13, Self::ES14, Self::ESNext, ] .into_iter() } }