Run PHPCS on the given buffer content and return LSP diagnostics. `file_path` is the real path of the file on disk. `content` is the current editor buffer (which may differ from the on-disk version). PHPCS reads from stdin when the `-` argument is given, and `--stdin-path` tells it the original filename for ruleset matching. `workspace_root` is needed to run PHPCS from the project root director
(
resolved: &ResolvedPhpcs,
content: &str,
file_path: &Path,
workspace_root: &Path,
config: &PhpcsConfig,
cancelled: &std::sync::atomic::AtomicBool,
)
| 145 | /// `workspace_root` is needed to run PHPCS from the project root |
| 146 | /// directory so that it picks up `phpcs.xml` / `phpcs.xml.dist`. |
| 147 | pub(crate) fn run_phpcs( |
| 148 | resolved: &ResolvedPhpcs, |
| 149 | content: &str, |
| 150 | file_path: &Path, |
| 151 | workspace_root: &Path, |
| 152 | config: &PhpcsConfig, |
| 153 | cancelled: &std::sync::atomic::AtomicBool, |
| 154 | ) -> Result<Vec<Diagnostic>, String> { |
| 155 | let timeout_ms = config.timeout.unwrap_or(DEFAULT_TIMEOUT_MS); |
| 156 | let timeout = Duration::from_millis(timeout_ms); |
| 157 | |
| 158 | let mut cmd = Command::new(&resolved.path); |
| 159 | cmd.arg("--report=json") |
| 160 | .arg("--no-colors") |
| 161 | .arg("-q") |
| 162 | .arg(format!("--stdin-path={}", file_path.display())) |
| 163 | .stdin(Stdio::piped()) |
| 164 | .stdout(Stdio::piped()) |
| 165 | .stderr(Stdio::piped()) |
| 166 | .current_dir(workspace_root); |
| 167 | |
| 168 | if let Some(ref standard) = config.standard { |
| 169 | cmd.arg(format!("--standard={}", standard)); |
| 170 | } |
| 171 | |
| 172 | // The `-` argument tells PHPCS to read from stdin. |
| 173 | cmd.arg("-"); |
| 174 | |
| 175 | let mut child = cmd |
| 176 | .spawn() |
| 177 | .map_err(|e| format!("Failed to spawn PHPCS: {}", e))?; |
| 178 | |
| 179 | // Write the buffer content to PHPCS's stdin, then close it. |
| 180 | if let Some(mut stdin) = child.stdin.take() { |
| 181 | // Use a separate scope so stdin is dropped (closed) before we wait. |
| 182 | std::io::Write::write_all(&mut stdin, content.as_bytes()) |
| 183 | .map_err(|e| format!("Failed to write to PHPCS stdin: {}", e))?; |
| 184 | } |
| 185 | |
| 186 | // Wait for the process with timeout. |
| 187 | let result = wait_with_timeout(&mut child, timeout, cancelled); |
| 188 | |
| 189 | match result { |
| 190 | Ok(output) => { |
| 191 | // PHPCS exit codes: |
| 192 | // 0 = no violations found |
| 193 | // 1 = violations found (warnings only) |
| 194 | // 2 = violations found (errors present) |
| 195 | // 3 = processing error |
| 196 | match output.code { |
| 197 | 0 => Ok(Vec::new()), |
| 198 | 1 | 2 => parse_phpcs_json(&output.stdout, file_path), |
| 199 | _ => { |
| 200 | // For other exit codes, try parsing JSON; fall back |
| 201 | // to error. |
| 202 | match parse_phpcs_json(&output.stdout, file_path) { |
| 203 | Ok(diags) if !diags.is_empty() => Ok(diags), |
| 204 | _ => Err(format!( |
no test coverage detected