1use crate::{
2 ra_ap_syntax::AstNode,
3 semver::{Error as SemVerError, Version as SemverVersion},
4 utils::{normalize, Error, INTERNAL_ERR},
5 Semantics, Version,
6};
7
8use log::{debug, info, trace};
9use oclif::term::{OUT_YELLOW, TERM_OUT};
10use ra_ap_base_db::{FileId, SourceDatabase, SourceDatabaseExt};
11use ra_ap_hir::Crate;
12use ra_ap_ide_db::symbol_index::SymbolsDatabase;
13use ra_ap_paths::AbsPathBuf;
14use ra_ap_project_model::{CargoConfig, ProjectManifest, ProjectWorkspace};
15use ra_ap_rust_analyzer::cli::load_cargo::{load_workspace, LoadCargoConfig};
16use ra_ap_text_edit::TextEdit;
17use rust_visitor::Visitor;
18
19use std::{
20 collections::HashMap as Map,
21 fs::{read_to_string, write},
22 path::Path,
23};
24
25mod context;
26mod helpers;
27mod visitor_impl;
28
29pub(crate) use context::Context;
30
31pub struct Runner {
32 pub(crate) minimum: Option<SemverVersion>,
33 pub(crate) versions: Vec<Version>,
34 version: SemverVersion,
35}
36
37impl Runner {
38 pub fn new() -> Self {
39 Self {
40 minimum: None,
41 versions: vec![],
42 version: SemverVersion::parse("0.0.0").expect(INTERNAL_ERR),
43 }
44 }
45
46 pub fn minimum(mut self, version: &str) -> Result<Self, SemVerError> {
47 self.minimum = Some(SemverVersion::parse(version)?);
48 Ok(self)
49 }
50
51 pub fn version(mut self, version: Version) -> Self {
52 self.versions.push(version);
53 self
54 }
55}
56
57impl Runner {
58 fn get_version(&self) -> Option<&Version> {
59 self.versions.iter().find(|x| x.version == self.version)
60 }
61}
62
63#[doc(hidden)]
64pub fn run(
65 root: &Path,
66 dep: &str,
67 mut runner: Runner,
68 from: SemverVersion,
69 to: SemverVersion,
70) -> Result<(), Error> {
71 info!("Workspace root: {}", root.display());
72
73 if let Some(min) = &runner.minimum {
74 if from < *min {
75 return Err(Error::NotMinimum(dep.into(), min.to_string()));
76 }
77 }
78
79 runner.version = to;
80 let version = runner.get_version();
81
82 let peers = if let Some(version) = version {
83 let mut peers = vec![dep.to_string()];
84 peers.extend(version.peers.clone());
85 peers
86 } else {
87 return Ok(TERM_OUT.write_line(&format!(
88 "Upgrader for crate {} has not described any changes for {} version",
89 OUT_YELLOW.apply_to(dep),
90 OUT_YELLOW.apply_to(runner.version),
91 ))?);
92 };
93
94 let manifest = ProjectManifest::discover_single(&AbsPathBuf::assert(root.into())).unwrap();
96
97 let no_progress = &|_| {};
98
99 let mut cargo_config = CargoConfig::default();
100 cargo_config.no_sysroot = true;
101
102 let mut workspace = ProjectWorkspace::load(manifest, &cargo_config, no_progress)?;
103 let bs = workspace.run_build_scripts(&cargo_config, no_progress)?;
104 workspace.set_build_scripts(bs);
105
106 let load_cargo_config = LoadCargoConfig {
107 load_out_dirs_from_check: true,
108 with_proc_macro: true,
109 prefill_caches: false,
110 };
111 let (host, vfs, _) = load_workspace(workspace, &load_cargo_config).unwrap();
112
113 let db = host.raw_database();
115 let semantics = Semantics::new(db);
116
117 let mut changes = Map::<FileId, TextEdit>::new();
118 let mut context = Context::new(runner, semantics);
119
120 context.init(&from)?;
122
123 trace!("Crate graph: {:#?}", db.crate_graph());
124
125 for krate in Crate::all(db) {
127 if let Some(name) = krate.display_name(db) {
128 debug!("Checking if we need to preload: {}", name);
129
130 if let Some(peer) = peers
131 .iter()
132 .find(|x| **x == normalize(&format!("{}", name)))
133 {
134 context.preloader.load(peer, db, &krate);
135 }
136 }
137 }
138
139 for source_root_id in db.local_roots().iter() {
141 let source_root = db.source_root(*source_root_id);
142 let krates = db.source_root_crates(*source_root_id);
143
144 if !krates
147 .iter()
148 .filter_map(|crate_id| {
149 let krate: Crate = (*crate_id).into();
150 source_root.path_for_file(&krate.root_file(db))
151 })
152 .filter_map(|path| path.as_path())
153 .any(|path| {
154 debug!("Checking if path in workspace: {}", path.display());
155 path.as_ref().starts_with(root)
156 })
157 {
158 continue;
159 }
160
161 for file_id in source_root.iter() {
162 let file = vfs.file_path(file_id);
163 info!("Walking: {}", file.as_path().expect(INTERNAL_ERR).display());
164
165 let source_file = context.semantics.parse(file_id);
166 trace!("Syntax: {:#?}", source_file.syntax());
167
168 context.walk(source_file.syntax());
169
170 let edit = context.upgrader.finish();
171 debug!("Changes to be made: {:#?}", edit);
172
173 changes.insert(file_id, edit);
174 }
175 }
176
177 for (file_id, edit) in changes {
179 let full_path = vfs.file_path(file_id);
180 let full_path = full_path.as_path().expect(INTERNAL_ERR);
181
182 let mut file_text = read_to_string(&full_path)?;
183
184 edit.apply(&mut file_text);
185 write(&full_path, file_text)?;
186 }
187
188 Ok(())
191}