Skip to content

Commit 732bdbd

Browse files
nayeemrmndsherret
andauthored
feat: compiler options from workspace member (#143)
Co-authored-by: David Sherret <[email protected]>
1 parent e48aa99 commit 732bdbd

File tree

3 files changed

+204
-217
lines changed

3 files changed

+204
-217
lines changed

src/deno_json/mod.rs

+64-38
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,6 @@ use crate::UrlToFilePathError;
3333

3434
mod ts;
3535

36-
pub use ts::CompilerOptions;
3736
pub use ts::EmitConfigOptions;
3837
pub use ts::IgnoredCompilerOptions;
3938
pub use ts::JsxImportSourceConfig;
@@ -64,6 +63,15 @@ pub enum IntoResolvedErrorKind {
6463
InvalidExclude(crate::glob::FromExcludeRelativePathOrPatternsError),
6564
}
6665

66+
#[derive(Debug, Error, JsError)]
67+
#[class(generic)]
68+
#[error("Failed deserilaizing \"compilerOptions\".\"types\" in {}", self.specifier)]
69+
pub struct CompilerOptionTypesDeserializeError {
70+
specifier: Url,
71+
#[source]
72+
source: serde_json::Error,
73+
}
74+
6775
#[derive(Clone, Debug, Default, Deserialize, PartialEq)]
6876
#[serde(default, deny_unknown_fields)]
6977
struct SerializedFilesConfig {
@@ -705,11 +713,20 @@ pub trait DenoJsonCache {
705713
fn set(&self, path: PathBuf, deno_json: ConfigFileRc);
706714
}
707715

716+
#[derive(Debug, Error, JsError)]
717+
#[class(type)]
718+
#[error("compilerOptions should be an object at '{specifier}'")]
719+
pub struct CompilerOptionsParseError {
720+
pub specifier: Url,
721+
#[source]
722+
pub source: serde_json::Error,
723+
}
724+
708725
#[derive(Debug, Error, JsError)]
709726
pub enum ConfigFileError {
710-
#[class(type)]
711-
#[error("compilerOptions should be an object: {0}")]
712-
CompilerOptionsShouldBeObject(serde_json::Error),
727+
#[class(inherit)]
728+
#[error(transparent)]
729+
CompilerOptionsParseError(CompilerOptionsParseError),
713730
#[class(type)]
714731
#[error("Only file: specifiers are supported for security reasons in import maps stored in a deno.json. To use a remote import map, use the --import-map flag and \"deno.importMap\" in the language server config")]
715732
OnlyFileSpecifiersSupported,
@@ -1014,26 +1031,29 @@ impl ConfigFile {
10141031
.to_path_buf()
10151032
}
10161033

1017-
/// Returns true if the configuration indicates that JavaScript should be
1018-
/// type checked, otherwise false.
1019-
pub fn get_check_js(&self) -> bool {
1034+
/// Returns if the configuration indicates that JavaScript should be
1035+
/// type checked, otherwise None if not set.
1036+
pub fn check_js(&self) -> Option<bool> {
10201037
self
10211038
.json
10221039
.compiler_options
10231040
.as_ref()
10241041
.and_then(|co| co.get("checkJs").and_then(|v| v.as_bool()))
1025-
.unwrap_or(false)
10261042
}
10271043

10281044
/// Parse `compilerOptions` and return a serde `Value`.
10291045
/// The result also contains any options that were ignored.
10301046
pub fn to_compiler_options(
10311047
&self,
1032-
) -> Result<ParsedTsConfigOptions, ConfigFileError> {
1048+
) -> Result<ParsedTsConfigOptions, CompilerOptionsParseError> {
10331049
if let Some(compiler_options) = self.json.compiler_options.clone() {
10341050
let options: serde_json::Map<String, Value> =
1035-
serde_json::from_value(compiler_options)
1036-
.map_err(ConfigFileError::CompilerOptionsShouldBeObject)?;
1051+
serde_json::from_value(compiler_options).map_err(|source| {
1052+
CompilerOptionsParseError {
1053+
specifier: self.specifier.clone(),
1054+
source,
1055+
}
1056+
})?;
10371057
Ok(parse_compiler_options(options, Some(&self.specifier)))
10381058
} else {
10391059
Ok(Default::default())
@@ -1580,20 +1600,27 @@ impl ConfigFile {
15801600

15811601
pub fn to_compiler_option_types(
15821602
&self,
1583-
) -> Result<Vec<(Url, Vec<String>)>, serde_json::Error> {
1603+
) -> Result<Option<(Url, Vec<String>)>, CompilerOptionTypesDeserializeError>
1604+
{
15841605
let Some(compiler_options_value) = self.json.compiler_options.as_ref()
15851606
else {
1586-
return Ok(Vec::new());
1607+
return Ok(None);
15871608
};
15881609
let Some(types) = compiler_options_value.get("types") else {
1589-
return Ok(Vec::new());
1610+
return Ok(None);
15901611
};
1591-
let imports: Vec<String> = serde_json::from_value(types.clone())?;
1612+
let imports: Vec<String> =
1613+
serde_json::from_value(types.clone()).map_err(|source| {
1614+
CompilerOptionTypesDeserializeError {
1615+
specifier: self.specifier.clone(),
1616+
source,
1617+
}
1618+
})?;
15921619
if !imports.is_empty() {
15931620
let referrer = self.specifier.clone();
1594-
Ok(vec![(referrer, imports)])
1621+
Ok(Some((referrer, imports)))
15951622
} else {
1596-
Ok(Vec::new())
1623+
Ok(None)
15971624
}
15981625
}
15991626

@@ -1603,14 +1630,22 @@ impl ConfigFile {
16031630
&self,
16041631
) -> Result<Option<JsxImportSourceConfig>, ToMaybeJsxImportSourceConfigError>
16051632
{
1633+
#[derive(Debug, Deserialize)]
1634+
#[serde(rename_all = "camelCase")]
1635+
struct JsxCompilerOptions {
1636+
pub jsx: Option<String>,
1637+
pub jsx_import_source: Option<String>,
1638+
pub jsx_import_source_types: Option<String>,
1639+
}
1640+
16061641
let Some(compiler_options_value) = self.json.compiler_options.as_ref()
16071642
else {
16081643
return Ok(None);
16091644
};
1610-
let Some(compiler_options) =
1611-
serde_json::from_value::<CompilerOptions>(compiler_options_value.clone())
1612-
.ok()
1613-
else {
1645+
let Some(compiler_options) = serde_json::from_value::<JsxCompilerOptions>(
1646+
compiler_options_value.clone(),
1647+
)
1648+
.ok() else {
16141649
return Ok(None);
16151650
};
16161651
let module = match compiler_options.jsx.as_deref() {
@@ -1745,6 +1780,7 @@ impl Serialize for TsTypeLib {
17451780
}
17461781

17471782
/// An enum that represents the base tsc configuration to return.
1783+
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
17481784
pub enum TsConfigType {
17491785
/// Return a configuration for bundling, using swc to emit the bundle. This is
17501786
/// independent of type checking.
@@ -1757,19 +1793,15 @@ pub enum TsConfigType {
17571793
}
17581794

17591795
#[derive(Debug, Clone, PartialEq, Eq)]
1760-
pub struct TsConfigForEmit {
1796+
pub struct TsConfigWithIgnoredOptions {
17611797
pub ts_config: TsConfig,
1762-
pub maybe_ignored_options: Option<IgnoredCompilerOptions>,
1798+
pub ignored_options: Vec<IgnoredCompilerOptions>,
17631799
}
17641800

1765-
/// For a given configuration type and optionally a configuration file,
1766-
/// return a `TsConfig` struct and optionally any user configuration
1767-
/// options that were ignored.
1768-
pub fn get_ts_config_for_emit(
1769-
config_type: TsConfigType,
1770-
maybe_config_file: Option<&ConfigFile>,
1771-
) -> Result<TsConfigForEmit, ConfigFileError> {
1772-
let mut ts_config = match config_type {
1801+
/// For a given configuration type get the starting point TsConfig
1802+
/// used that can then be merged with user specified tsconfigs.
1803+
pub fn get_base_ts_config_for_emit(config_type: TsConfigType) -> TsConfig {
1804+
match config_type {
17731805
TsConfigType::Bundle => TsConfig::new(json!({
17741806
"allowImportingTsExtensions": true,
17751807
"checkJs": false,
@@ -1827,13 +1859,7 @@ pub fn get_ts_config_for_emit(
18271859
"moduleResolution": "NodeNext",
18281860
"resolveJsonModule": true,
18291861
})),
1830-
};
1831-
let maybe_ignored_options =
1832-
ts_config.merge_tsconfig_from_config_file(maybe_config_file)?;
1833-
Ok(TsConfigForEmit {
1834-
ts_config,
1835-
maybe_ignored_options,
1836-
})
1862+
}
18371863
}
18381864

18391865
#[cfg(test)]

src/deno_json/ts.rs

+16-81
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,9 @@
11
// Copyright 2018-2024 the Deno authors. MIT license.
22

3-
use crate::deno_json::ConfigFileError;
43
use serde::Deserialize;
54
use serde::Serialize;
65
use serde::Serializer;
7-
use serde_json::json;
86
use serde_json::Value;
9-
use std::collections::BTreeMap;
107
use std::fmt;
118
use url::Url;
129

@@ -55,17 +52,6 @@ pub struct EmitConfigOptions {
5552
pub jsx_precompile_skip_elements: Option<Vec<String>>,
5653
}
5754

58-
/// There are certain compiler options that can impact what modules are part of
59-
/// a module graph, which need to be deserialized into a structure for analysis.
60-
#[derive(Debug, Deserialize)]
61-
#[serde(rename_all = "camelCase")]
62-
pub struct CompilerOptions {
63-
pub jsx: Option<String>,
64-
pub jsx_import_source: Option<String>,
65-
pub jsx_import_source_types: Option<String>,
66-
pub types: Option<Vec<String>>,
67-
}
68-
6955
/// A structure that represents a set of options that were ignored and the
7056
/// path those options came from.
7157
#[derive(Debug, Clone, Eq, PartialEq)]
@@ -143,7 +129,7 @@ pub fn parse_compiler_options(
143129
) -> ParsedTsConfigOptions {
144130
let mut allowed: serde_json::Map<String, Value> =
145131
serde_json::Map::with_capacity(compiler_options.len());
146-
let mut ignored: Vec<String> = Vec::with_capacity(compiler_options.len());
132+
let mut ignored: Vec<String> = Vec::new(); // don't pre-allocate because it's rare
147133

148134
for (key, value) in compiler_options {
149135
// We don't pass "types" entries to typescript via the compiler
@@ -177,63 +163,31 @@ pub fn parse_compiler_options(
177163
}
178164

179165
/// A structure for managing the configuration of TypeScript
180-
#[derive(Debug, Clone, PartialEq, Eq)]
166+
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
181167
pub struct TsConfig(pub Value);
182168

169+
impl Default for TsConfig {
170+
fn default() -> Self {
171+
Self(serde_json::Value::Object(Default::default()))
172+
}
173+
}
174+
183175
impl TsConfig {
184176
/// Create a new `TsConfig` with the base being the `value` supplied.
185177
pub fn new(value: Value) -> Self {
186178
TsConfig(value)
187179
}
188180

189-
pub fn as_bytes(&self) -> Vec<u8> {
190-
let map = self.0.as_object().expect("invalid tsconfig");
191-
let ordered: BTreeMap<_, _> = map.iter().collect();
192-
let value = json!(ordered);
193-
value.to_string().as_bytes().to_owned()
194-
}
195-
196-
/// Return the value of the `checkJs` compiler option, defaulting to `false`
197-
/// if not present.
198-
pub fn get_check_js(&self) -> bool {
199-
if let Some(check_js) = self.0.get("checkJs") {
200-
check_js.as_bool().unwrap_or(false)
201-
} else {
202-
false
203-
}
204-
}
205-
206-
pub fn get_declaration(&self) -> bool {
207-
if let Some(declaration) = self.0.get("declaration") {
208-
declaration.as_bool().unwrap_or(false)
209-
} else {
210-
false
211-
}
181+
pub fn merge_mut(&mut self, value: TsConfig) {
182+
json_merge(&mut self.0, value.0);
212183
}
213184

214185
/// Merge a serde_json value into the configuration.
215-
pub fn merge(&mut self, value: serde_json::Value) {
216-
json_merge(&mut self.0, value);
217-
}
218-
219-
/// Take an optional user provided config file
220-
/// which was passed in via the `--config` flag and merge `compilerOptions` with
221-
/// the configuration. Returning the result which optionally contains any
222-
/// compiler options that were ignored.
223-
pub fn merge_tsconfig_from_config_file(
186+
pub fn merge_object_mut(
224187
&mut self,
225-
maybe_config_file: Option<&super::ConfigFile>,
226-
) -> Result<Option<IgnoredCompilerOptions>, ConfigFileError> {
227-
if let Some(config_file) = maybe_config_file {
228-
let ParsedTsConfigOptions {
229-
options,
230-
maybe_ignored,
231-
} = config_file.to_compiler_options()?;
232-
self.merge(serde_json::Value::Object(options));
233-
Ok(maybe_ignored)
234-
} else {
235-
Ok(None)
236-
}
188+
value: serde_json::Map<String, serde_json::Value>,
189+
) {
190+
json_merge(&mut self.0, serde_json::Value::Object(value));
237191
}
238192
}
239193

@@ -263,28 +217,9 @@ fn json_merge(a: &mut Value, b: Value) {
263217

264218
#[cfg(test)]
265219
mod tests {
266-
use super::*;
220+
use serde_json::json;
267221

268-
#[test]
269-
fn test_tsconfig_as_bytes() {
270-
let mut tsconfig1 = TsConfig::new(json!({
271-
"strict": true,
272-
"target": "esnext",
273-
}));
274-
tsconfig1.merge(json!({
275-
"target": "es5",
276-
"module": "amd",
277-
}));
278-
let mut tsconfig2 = TsConfig::new(json!({
279-
"target": "esnext",
280-
"strict": true,
281-
}));
282-
tsconfig2.merge(json!({
283-
"module": "amd",
284-
"target": "es5",
285-
}));
286-
assert_eq!(tsconfig1.as_bytes(), tsconfig2.as_bytes());
287-
}
222+
use super::*;
288223

289224
#[test]
290225
fn test_json_merge() {

0 commit comments

Comments
 (0)